mirror of
https://github.com/invoke-ai/InvokeAI.git
synced 2026-04-23 03:00:31 -04:00
feat(ui): handle FLUX bbox constraints
- Update canvas slice's to track the current base model architecture instead of just the optimal dimension. This lets us derive both optimal dimension _and_ grid size for the currently selected model. - Update all bbox size utilities to use derived grid size instead of hardcoded values of 8 or 64 - Review every damned instance of the number 8 in the whole frontend and update the ones that need to use the grid size - Update the invoke button blocking logic to check against scaled bbox size, unless scaling is disabled. - Update the invoke button blocking to say if it's width or height that is invalid and if its bbox or scaled, for both FLUX and the T2I adapter constraints
This commit is contained in:
committed by
Kent Keirsey
parent
18bb69f0d5
commit
4ee248b736
@@ -1006,7 +1006,11 @@
|
||||
"noFLUXVAEModelSelected": "No VAE model selected for FLUX generation",
|
||||
"noCLIPEmbedModelSelected": "No CLIP Embed model selected for FLUX generation",
|
||||
"canvasManagerNotLoaded": "Canvas Manager not loaded",
|
||||
"fluxModelIncompatibleDimensions": "FLUX requires image dimension to be multiples of 16",
|
||||
"fluxRequiresDimensionsToBeMultipleOf16": "FLUX requires width/height to be multiple of 16",
|
||||
"fluxModelIncompatibleBboxWidth": "$t(parameters.invoke.fluxRequiresDimensionsToBeMultipleOf16), bbox width is {{width}}",
|
||||
"fluxModelIncompatibleBboxHeight": "$t(parameters.invoke.fluxRequiresDimensionsToBeMultipleOf16), bbox height is {{height}}",
|
||||
"fluxModelIncompatibleScaledWidth": "$t(parameters.invoke.fluxRequiresDimensionsToBeMultipleOf16), scaled bbox width is {{width}}",
|
||||
"fluxModelIncompatibleScaledHeight": "$t(parameters.invoke.fluxRequiresDimensionsToBeMultipleOf16), scaled bbox height is {{height}}",
|
||||
"canvasIsFiltering": "Canvas is filtering",
|
||||
"canvasIsTransforming": "Canvas is transforming",
|
||||
"canvasIsRasterizing": "Canvas is rasterizing",
|
||||
@@ -1020,7 +1024,11 @@
|
||||
"controlAdapterIncompatibleBaseModel": "incompatible Control Adapter base model",
|
||||
"controlAdapterNoImageSelected": "no Control Adapter image selected",
|
||||
"controlAdapterImageNotProcessed": "Control Adapter image not processed",
|
||||
"t2iAdapterIncompatibleDimensions": "T2I Adapter requires image dimension to be multiples of {{multiple}}",
|
||||
"t2iAdapterRequiresDimensionsToBeMultipleOf": "T2I Adapter requires width/height to be multiple of",
|
||||
"t2iAdapterIncompatibleBboxWidth": "$t(parameters.invoke.layer.t2iAdapterRequiresDimensionsToBeMultipleOf) {{multiple}}, bbox width is {{width}}",
|
||||
"t2iAdapterIncompatibleBboxHeight": "$t(parameters.invoke.layer.t2iAdapterRequiresDimensionsToBeMultipleOf) {{multiple}}, bbox height is {{height}}",
|
||||
"t2iAdapterIncompatibleScaledBboxWidth": "$t(parameters.invoke.layer.t2iAdapterRequiresDimensionsToBeMultipleOf) {{multiple}}, scaled bbox width is {{width}}",
|
||||
"t2iAdapterIncompatibleScaledBboxHeight": "$t(parameters.invoke.layer.t2iAdapterRequiresDimensionsToBeMultipleOf) {{multiple}}, scaled bbox height is {{height}}",
|
||||
"ipAdapterNoModelSelected": "no IP adapter selected",
|
||||
"ipAdapterIncompatibleBaseModel": "incompatible IP Adapter base model",
|
||||
"ipAdapterNoImageSelected": "no IP Adapter image selected",
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import { logger } from 'app/logging/logger';
|
||||
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
||||
import { bboxOptimalDimensionChanged, bboxSyncedToOptimalDimension } from 'features/controlLayers/store/canvasSlice';
|
||||
import { bboxSyncedToOptimalDimension } from 'features/controlLayers/store/canvasSlice';
|
||||
import { selectIsStaging } from 'features/controlLayers/store/canvasStagingAreaSlice';
|
||||
import { loraDeleted } from 'features/controlLayers/store/lorasSlice';
|
||||
import { modelChanged, vaeSelected } from 'features/controlLayers/store/paramsSlice';
|
||||
import { modelSelected } from 'features/parameters/store/actions';
|
||||
import { zParameterModel } from 'features/parameters/types/parameterSchemas';
|
||||
import { getOptimalDimension } from 'features/parameters/util/optimalDimension';
|
||||
import { toast } from 'features/toast/toast';
|
||||
import { t } from 'i18next';
|
||||
|
||||
@@ -71,8 +70,6 @@ export const addModelSelectedListener = (startAppListening: AppStartListening) =
|
||||
}
|
||||
|
||||
dispatch(modelChanged({ model: newModel, previousModel: state.params.model }));
|
||||
// When staging, we don't want to change the bbox, but we must keep the optimal dimension in sync.
|
||||
dispatch(bboxOptimalDimensionChanged({ optimalDimension: getOptimalDimension(newModel) }));
|
||||
if (!selectIsStaging(state)) {
|
||||
dispatch(bboxSyncedToOptimalDimension());
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ import type { AppStartListening } from 'app/store/middleware/listenerMiddleware'
|
||||
import type { AppDispatch, RootState } from 'app/store/store';
|
||||
import type { SerializableObject } from 'common/types';
|
||||
import {
|
||||
bboxOptimalDimensionChanged,
|
||||
bboxSyncedToOptimalDimension,
|
||||
controlLayerModelChanged,
|
||||
referenceImageIPAdapterModelChanged,
|
||||
@@ -29,7 +28,6 @@ import {
|
||||
zParameterT5EncoderModel,
|
||||
zParameterVAEModel,
|
||||
} from 'features/parameters/types/parameterSchemas';
|
||||
import { getOptimalDimension } from 'features/parameters/util/optimalDimension';
|
||||
import type { Logger } from 'roarr';
|
||||
import { modelConfigsAdapterSelectors, modelsApi } from 'services/api/endpoints/models';
|
||||
import type { AnyModelConfig } from 'services/api/types';
|
||||
@@ -123,8 +121,6 @@ const handleMainModels: ModelHandler = (models, state, dispatch, log) => {
|
||||
'No selected main model or selected main model is not available, selecting default model'
|
||||
);
|
||||
dispatch(modelChanged({ model: zParameterModel.parse(defaultModel), previousModel: selectedMainModel }));
|
||||
// When staging, we don't want to change the bbox, but we must keep the optimal dimension in sync.
|
||||
dispatch(bboxOptimalDimensionChanged({ optimalDimension: getOptimalDimension(defaultModel) }));
|
||||
if (!selectIsStaging(state)) {
|
||||
dispatch(bboxSyncedToOptimalDimension());
|
||||
}
|
||||
@@ -137,8 +133,6 @@ const handleMainModels: ModelHandler = (models, state, dispatch, log) => {
|
||||
'No selected main model or selected main model is not available, selecting first available model'
|
||||
);
|
||||
dispatch(modelChanged({ model: zParameterModel.parse(firstModel), previousModel: selectedMainModel }));
|
||||
// When staging, we don't want to change the bbox, but we must keep the optimal dimension in sync.
|
||||
dispatch(bboxOptimalDimensionChanged({ optimalDimension: getOptimalDimension(firstModel) }));
|
||||
if (!selectIsStaging(state)) {
|
||||
dispatch(bboxSyncedToOptimalDimension());
|
||||
}
|
||||
|
||||
@@ -157,8 +157,32 @@ const createSelector = (
|
||||
if (!params.fluxVAE) {
|
||||
reasons.push({ content: i18n.t('parameters.invoke.noFLUXVAEModelSelected') });
|
||||
}
|
||||
if (bbox.rect.width % 16 !== 0 || bbox.rect.height % 16 !== 0) {
|
||||
reasons.push({ content: i18n.t('parameters.invoke.fluxModelIncompatibleDimensions') });
|
||||
if (bbox.scaleMethod === 'none') {
|
||||
if (bbox.rect.width % 16 !== 0) {
|
||||
reasons.push({
|
||||
content: i18n.t('parameters.invoke.fluxModelIncompatibleBboxWidth', { width: bbox.rect.width }),
|
||||
});
|
||||
}
|
||||
if (bbox.rect.height % 16 !== 0) {
|
||||
reasons.push({
|
||||
content: i18n.t('parameters.invoke.fluxModelIncompatibleBboxHeight', { height: bbox.rect.height }),
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (bbox.scaledSize.width % 16 !== 0) {
|
||||
reasons.push({
|
||||
content: i18n.t('parameters.invoke.fluxModelIncompatibleScaledBboxWidth', {
|
||||
width: bbox.scaledSize.width,
|
||||
}),
|
||||
});
|
||||
}
|
||||
if (bbox.scaledSize.height % 16 !== 0) {
|
||||
reasons.push({
|
||||
content: i18n.t('parameters.invoke.fluxModelIncompatibleScaledBboxHeight', {
|
||||
height: bbox.scaledSize.height,
|
||||
}),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,8 +205,40 @@ const createSelector = (
|
||||
// T2I Adapters require images have dimensions that are multiples of 64 (SD1.5) or 32 (SDXL)
|
||||
if (controlLayer.controlAdapter.type === 't2i_adapter') {
|
||||
const multiple = model?.base === 'sdxl' ? 32 : 64;
|
||||
if (bbox.rect.width % multiple !== 0 || bbox.rect.height % multiple !== 0) {
|
||||
problems.push(i18n.t('parameters.invoke.layer.t2iAdapterIncompatibleDimensions', { multiple }));
|
||||
if (bbox.scaleMethod === 'none') {
|
||||
if (bbox.rect.width % 16 !== 0) {
|
||||
reasons.push({
|
||||
content: i18n.t('parameters.invoke.layer.t2iAdapterIncompatibleBboxWidth', {
|
||||
multiple,
|
||||
width: bbox.rect.width,
|
||||
}),
|
||||
});
|
||||
}
|
||||
if (bbox.rect.height % 16 !== 0) {
|
||||
reasons.push({
|
||||
content: i18n.t('parameters.invoke.layer.t2iAdapterIncompatibleBboxHeight', {
|
||||
multiple,
|
||||
height: bbox.rect.height,
|
||||
}),
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (bbox.scaledSize.width % 16 !== 0) {
|
||||
reasons.push({
|
||||
content: i18n.t('parameters.invoke.layer.t2iAdapterIncompatibleScaledBboxWidth', {
|
||||
multiple,
|
||||
width: bbox.scaledSize.width,
|
||||
}),
|
||||
});
|
||||
}
|
||||
if (bbox.scaledSize.height % 16 !== 0) {
|
||||
reasons.push({
|
||||
content: i18n.t('parameters.invoke.layer.t2iAdapterIncompatibleScaledBboxHeight', {
|
||||
multiple,
|
||||
height: bbox.scaledSize.height,
|
||||
}),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -241,6 +241,8 @@ export class CanvasBboxModule extends CanvasModuleBase {
|
||||
* - Pushes the new bbox rect into app state
|
||||
*/
|
||||
onDragMove = () => {
|
||||
// The grid size here is the _position_ grid size, not the _dimension_ grid size - it is not constratined by the
|
||||
// currently-selected model.
|
||||
const gridSize = this.manager.stateApi.$ctrlKey.get() || this.manager.stateApi.$metaKey.get() ? 8 : 64;
|
||||
const bbox = this.manager.stateApi.getBbox();
|
||||
const bboxRect: Rect = {
|
||||
@@ -277,7 +279,7 @@ export class CanvasBboxModule extends CanvasModuleBase {
|
||||
const shift = this.manager.stateApi.$shiftKey.get();
|
||||
|
||||
// Grid size depends on the modifier keys
|
||||
let gridSize = ctrl || meta ? 8 : 64;
|
||||
let gridSize = ctrl || meta ? this.manager.stateApi.getBboxGridSize() : 64;
|
||||
|
||||
// Alt key indicates we are using centered scaling. We need to double the gride size used when calculating the
|
||||
// new dimensions so that each size scales in the correct increments and doesn't mis-place the bbox. For example, if
|
||||
@@ -384,7 +386,7 @@ export class CanvasBboxModule extends CanvasModuleBase {
|
||||
|
||||
// Determine the bbox size that fits within the visible rect. The bbox must be at least 64px in width and height,
|
||||
// and its width and height must be multiples of 8px.
|
||||
const gridSize = 8;
|
||||
const gridSize = this.manager.stateApi.getBboxGridSize();
|
||||
|
||||
// To be conservative, we will round up the x and y to the nearest grid size, and round down the width and height.
|
||||
// This ensures the bbox is never _larger_ than the visible rect. If the bbox is larger than the visible, we
|
||||
@@ -407,8 +409,12 @@ export class CanvasBboxModule extends CanvasModuleBase {
|
||||
const stage = this.konva.transformer.getStage();
|
||||
assert(stage, 'Stage must exist');
|
||||
|
||||
// We need to snap the anchors to the grid. If the user is holding ctrl/meta, we use the finer 8px grid.
|
||||
const gridSize = this.manager.stateApi.$ctrlKey.get() || this.manager.stateApi.$metaKey.get() ? 8 : 64;
|
||||
// We need to snap the anchors to the grid. If the user is holding ctrl/meta, we use the finest grid size allowed
|
||||
// currently-selected model.
|
||||
const gridSize =
|
||||
this.manager.stateApi.$ctrlKey.get() || this.manager.stateApi.$metaKey.get()
|
||||
? this.manager.stateApi.getBboxGridSize()
|
||||
: 64;
|
||||
// Because we are working in absolute coordinates, we need to scale the grid size by the stage scale.
|
||||
const scaledGridSize = gridSize * stage.scaleX();
|
||||
// To snap the anchor to the grid, we need to calculate an offset from the stage's absolute position.
|
||||
|
||||
@@ -25,7 +25,12 @@ import {
|
||||
entityReset,
|
||||
} from 'features/controlLayers/store/canvasSlice';
|
||||
import { selectCanvasStagingAreaSlice } from 'features/controlLayers/store/canvasStagingAreaSlice';
|
||||
import { selectAllRenderableEntities, selectBbox, selectCanvasSlice } from 'features/controlLayers/store/selectors';
|
||||
import {
|
||||
selectAllRenderableEntities,
|
||||
selectBbox,
|
||||
selectCanvasSlice,
|
||||
selectGridSize,
|
||||
} from 'features/controlLayers/store/selectors';
|
||||
import type {
|
||||
CanvasEntityType,
|
||||
CanvasState,
|
||||
@@ -401,6 +406,10 @@ export class CanvasStateApiModule extends CanvasModuleBase {
|
||||
return this.runSelector(selectCanvasSettingsSlice);
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the _positional_ grid size for the current canvas. Note that this is not the same as bbox grid size, which is
|
||||
* based on the currently-selected model.
|
||||
*/
|
||||
getGridSize = (): number => {
|
||||
const snapToGrid = this.getSettings().snapToGrid;
|
||||
if (!snapToGrid) {
|
||||
@@ -448,6 +457,13 @@ export class CanvasStateApiModule extends CanvasModuleBase {
|
||||
return this.runSelector(selectCanvasStagingAreaSlice);
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the grid size for the current canvas, based on the currently-selected model
|
||||
*/
|
||||
getBboxGridSize = (): number => {
|
||||
return this.runSelector(selectGridSize);
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if an entity is selected.
|
||||
*/
|
||||
|
||||
@@ -6,6 +6,7 @@ import { deepClone } from 'common/util/deepClone';
|
||||
import { roundDownToMultiple, roundToMultiple } from 'common/util/roundDownToMultiple';
|
||||
import { getPrefixedId } from 'features/controlLayers/konva/util';
|
||||
import { canvasReset } from 'features/controlLayers/store/actions';
|
||||
import { modelChanged } from 'features/controlLayers/store/paramsSlice';
|
||||
import {
|
||||
selectAllEntities,
|
||||
selectAllEntitiesOfType,
|
||||
@@ -19,12 +20,15 @@ import type {
|
||||
RegionalGuidanceReferenceImageState,
|
||||
RgbColor,
|
||||
} from 'features/controlLayers/store/types';
|
||||
import { getScaledBoundingBoxDimensions } from 'features/controlLayers/util/getScaledBoundingBoxDimensions';
|
||||
import {
|
||||
calculateNewSize,
|
||||
getScaledBoundingBoxDimensions,
|
||||
} from 'features/controlLayers/util/getScaledBoundingBoxDimensions';
|
||||
import { simplifyFlatNumbersArray } from 'features/controlLayers/util/simplify';
|
||||
import { zModelIdentifierField } from 'features/nodes/types/common';
|
||||
import { calculateNewSize } from 'features/parameters/components/Bbox/calculateNewSize';
|
||||
import type { MainModelBase } from 'features/nodes/types/common';
|
||||
import { isMainModelBase, zModelIdentifierField } from 'features/nodes/types/common';
|
||||
import { ASPECT_RATIO_MAP } from 'features/parameters/components/Bbox/constants';
|
||||
import { getIsSizeOptimal } from 'features/parameters/util/optimalDimension';
|
||||
import { getGridSize, getIsSizeOptimal, getOptimalDimension } from 'features/parameters/util/optimalDimension';
|
||||
import type { IRect } from 'konva/lib/types';
|
||||
import { merge, omit } from 'lodash-es';
|
||||
import type { UndoableOptions } from 'redux-undo';
|
||||
@@ -89,7 +93,6 @@ const getInitialState = (): CanvasState => {
|
||||
referenceImages: { entities: [] },
|
||||
bbox: {
|
||||
rect: { x: 0, y: 0, width: 512, height: 512 },
|
||||
optimalDimension: 512,
|
||||
aspectRatio: {
|
||||
id: '1:1',
|
||||
value: 1,
|
||||
@@ -100,6 +103,7 @@ const getInitialState = (): CanvasState => {
|
||||
width: 512,
|
||||
height: 512,
|
||||
},
|
||||
modelBase: 'sd-1',
|
||||
},
|
||||
};
|
||||
return initialState;
|
||||
@@ -629,15 +633,27 @@ export const canvasSlice = createSlice({
|
||||
},
|
||||
//#region BBox
|
||||
bboxScaledWidthChanged: (state, action: PayloadAction<number>) => {
|
||||
state.bbox.scaledSize.width = action.payload;
|
||||
const gridSize = getGridSize(state.bbox.modelBase);
|
||||
|
||||
state.bbox.scaledSize.width = roundToMultiple(action.payload, gridSize);
|
||||
|
||||
if (state.bbox.aspectRatio.isLocked) {
|
||||
state.bbox.scaledSize.height = roundToMultiple(state.bbox.scaledSize.width / state.bbox.aspectRatio.value, 8);
|
||||
state.bbox.scaledSize.height = roundToMultiple(
|
||||
state.bbox.scaledSize.width / state.bbox.aspectRatio.value,
|
||||
gridSize
|
||||
);
|
||||
}
|
||||
},
|
||||
bboxScaledHeightChanged: (state, action: PayloadAction<number>) => {
|
||||
state.bbox.scaledSize.height = action.payload;
|
||||
const gridSize = getGridSize(state.bbox.modelBase);
|
||||
|
||||
state.bbox.scaledSize.height = roundToMultiple(action.payload, gridSize);
|
||||
|
||||
if (state.bbox.aspectRatio.isLocked) {
|
||||
state.bbox.scaledSize.width = roundToMultiple(state.bbox.scaledSize.height * state.bbox.aspectRatio.value, 8);
|
||||
state.bbox.scaledSize.width = roundToMultiple(
|
||||
state.bbox.scaledSize.height * state.bbox.aspectRatio.value,
|
||||
gridSize
|
||||
);
|
||||
}
|
||||
},
|
||||
bboxScaleMethodChanged: (state, action: PayloadAction<BoundingBoxScaleMethod>) => {
|
||||
@@ -660,10 +676,11 @@ export const canvasSlice = createSlice({
|
||||
action: PayloadAction<{ width: number; updateAspectRatio?: boolean; clamp?: boolean }>
|
||||
) => {
|
||||
const { width, updateAspectRatio, clamp } = action.payload;
|
||||
state.bbox.rect.width = clamp ? Math.max(roundDownToMultiple(width, 8), 64) : width;
|
||||
const gridSize = getGridSize(state.bbox.modelBase);
|
||||
state.bbox.rect.width = clamp ? Math.max(roundDownToMultiple(width, gridSize), 64) : width;
|
||||
|
||||
if (state.bbox.aspectRatio.isLocked) {
|
||||
state.bbox.rect.height = roundToMultiple(state.bbox.rect.width / state.bbox.aspectRatio.value, 8);
|
||||
state.bbox.rect.height = roundToMultiple(state.bbox.rect.width / state.bbox.aspectRatio.value, gridSize);
|
||||
}
|
||||
|
||||
if (updateAspectRatio || !state.bbox.aspectRatio.isLocked) {
|
||||
@@ -679,11 +696,11 @@ export const canvasSlice = createSlice({
|
||||
action: PayloadAction<{ height: number; updateAspectRatio?: boolean; clamp?: boolean }>
|
||||
) => {
|
||||
const { height, updateAspectRatio, clamp } = action.payload;
|
||||
|
||||
state.bbox.rect.height = clamp ? Math.max(roundDownToMultiple(height, 8), 64) : height;
|
||||
const gridSize = getGridSize(state.bbox.modelBase);
|
||||
state.bbox.rect.height = clamp ? Math.max(roundDownToMultiple(height, gridSize), 64) : height;
|
||||
|
||||
if (state.bbox.aspectRatio.isLocked) {
|
||||
state.bbox.rect.width = roundToMultiple(state.bbox.rect.height * state.bbox.aspectRatio.value, 8);
|
||||
state.bbox.rect.width = roundToMultiple(state.bbox.rect.height * state.bbox.aspectRatio.value, gridSize);
|
||||
}
|
||||
|
||||
if (updateAspectRatio || !state.bbox.aspectRatio.isLocked) {
|
||||
@@ -708,7 +725,8 @@ export const canvasSlice = createSlice({
|
||||
state.bbox.aspectRatio.value = ASPECT_RATIO_MAP[id].ratio;
|
||||
const { width, height } = calculateNewSize(
|
||||
state.bbox.aspectRatio.value,
|
||||
state.bbox.rect.width * state.bbox.rect.height
|
||||
state.bbox.rect.width * state.bbox.rect.height,
|
||||
state.bbox.modelBase
|
||||
);
|
||||
state.bbox.rect.width = width;
|
||||
state.bbox.rect.height = height;
|
||||
@@ -726,7 +744,8 @@ export const canvasSlice = createSlice({
|
||||
} else {
|
||||
const { width, height } = calculateNewSize(
|
||||
state.bbox.aspectRatio.value,
|
||||
state.bbox.rect.width * state.bbox.rect.height
|
||||
state.bbox.rect.width * state.bbox.rect.height,
|
||||
state.bbox.modelBase
|
||||
);
|
||||
state.bbox.rect.width = width;
|
||||
state.bbox.rect.height = height;
|
||||
@@ -736,33 +755,37 @@ export const canvasSlice = createSlice({
|
||||
syncScaledSize(state);
|
||||
},
|
||||
bboxSizeOptimized: (state) => {
|
||||
const optimalDimension = getOptimalDimension(state.bbox.modelBase);
|
||||
if (state.bbox.aspectRatio.isLocked) {
|
||||
const { width, height } = calculateNewSize(state.bbox.aspectRatio.value, state.bbox.optimalDimension ** 2);
|
||||
const { width, height } = calculateNewSize(
|
||||
state.bbox.aspectRatio.value,
|
||||
optimalDimension * optimalDimension,
|
||||
state.bbox.modelBase
|
||||
);
|
||||
state.bbox.rect.width = width;
|
||||
state.bbox.rect.height = height;
|
||||
} else {
|
||||
state.bbox.aspectRatio = deepClone(initialState.bbox.aspectRatio);
|
||||
state.bbox.rect.width = state.bbox.optimalDimension;
|
||||
state.bbox.rect.height = state.bbox.optimalDimension;
|
||||
state.bbox.rect.width = optimalDimension;
|
||||
state.bbox.rect.height = optimalDimension;
|
||||
}
|
||||
|
||||
syncScaledSize(state);
|
||||
},
|
||||
bboxOptimalDimensionChanged: (state, action: PayloadAction<{ optimalDimension: number }>) => {
|
||||
// When staging, we don't want to change the bbox, but we must keep the optimal dimension in sync.
|
||||
// This action does the syncing. `bboxSyncedToOptimalDimension` below will actually change the bbox,
|
||||
// and is only called when we are not staging.
|
||||
const { optimalDimension } = action.payload;
|
||||
state.bbox.optimalDimension = optimalDimension;
|
||||
|
||||
// But! We do want to update the _scaled_ size. This handles the case where the user changes the base model type
|
||||
// during staging. Though the generation bbox must be unchanged, the scaled bbox should adapt to the model.
|
||||
bboxModelBaseChanged: (state, action: PayloadAction<{ modelBase: MainModelBase }>) => {
|
||||
const { modelBase } = action.payload;
|
||||
state.bbox.modelBase = modelBase;
|
||||
syncScaledSize(state);
|
||||
},
|
||||
bboxSyncedToOptimalDimension: (state) => {
|
||||
const { optimalDimension } = state.bbox;
|
||||
if (!getIsSizeOptimal(state.bbox.rect.width, state.bbox.rect.height, optimalDimension)) {
|
||||
const bboxDims = calculateNewSize(state.bbox.aspectRatio.value, optimalDimension * optimalDimension);
|
||||
const optimalDimension = getOptimalDimension(state.bbox.modelBase);
|
||||
|
||||
if (!getIsSizeOptimal(state.bbox.rect.width, state.bbox.rect.height, state.bbox.modelBase)) {
|
||||
const bboxDims = calculateNewSize(
|
||||
state.bbox.aspectRatio.value,
|
||||
optimalDimension * optimalDimension,
|
||||
state.bbox.modelBase
|
||||
);
|
||||
state.bbox.rect.width = bboxDims.width;
|
||||
state.bbox.rect.height = bboxDims.height;
|
||||
syncScaledSize(state);
|
||||
@@ -1073,6 +1096,32 @@ export const canvasSlice = createSlice({
|
||||
builder.addCase(canvasReset, (state) => {
|
||||
return resetState(state);
|
||||
});
|
||||
builder.addCase(modelChanged, (state, action) => {
|
||||
const { model } = action.payload;
|
||||
/**
|
||||
* Because the bbox depends in part on the model, it needs to be in sync with the model. However, due to
|
||||
* complications with managing undo/redo history, we need to store the model in a separate slice from the canvas
|
||||
* state.
|
||||
*
|
||||
* Unfortunately, this means we need to manually sync the model with the canvas state. We only care about the
|
||||
* model base, so we only need to update the bbox's modelBase field.
|
||||
*
|
||||
* When we do this, we also want to update the bbox's dimensions - but only if we are not staging images on the
|
||||
* canvas, during which time the bbox must stay the same.
|
||||
*
|
||||
* Unfortunately (again), the staging state is in a different slice, to prevent issues with undo/redo history.
|
||||
*
|
||||
* There's some fanagling we must do to handle this correctly:
|
||||
* - Store the model base in this slice, so that we can access it when the user changes the bbox dimensions.
|
||||
* - Avoid updating the bbox dimensions when we are staging - only update the model base.
|
||||
* - Provide a separate action that will update the bbox dimensions and be careful to not dispatch it when staging.
|
||||
*/
|
||||
const base = model?.base;
|
||||
if (isMainModelBase(base) && state.bbox.modelBase !== base) {
|
||||
state.bbox.modelBase = base;
|
||||
syncScaledSize(state);
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1081,10 +1130,12 @@ const resetState = (state: CanvasState) => {
|
||||
|
||||
// We need to retain the optimal dimension across resets, as it is changed only when the model changes. Copy it
|
||||
// from the old state, then recalculate the bbox size & scaled size.
|
||||
newState.bbox.optimalDimension = state.bbox.optimalDimension;
|
||||
newState.bbox.modelBase = state.bbox.modelBase;
|
||||
const optimalDimension = getOptimalDimension(newState.bbox.modelBase);
|
||||
const rect = calculateNewSize(
|
||||
newState.bbox.aspectRatio.value,
|
||||
newState.bbox.optimalDimension * newState.bbox.optimalDimension
|
||||
optimalDimension * optimalDimension,
|
||||
newState.bbox.modelBase
|
||||
);
|
||||
newState.bbox.rect.width = rect.width;
|
||||
newState.bbox.rect.height = rect.height;
|
||||
@@ -1132,7 +1183,6 @@ export const {
|
||||
bboxAspectRatioIdChanged,
|
||||
bboxDimensionsSwapped,
|
||||
bboxSizeOptimized,
|
||||
bboxOptimalDimensionChanged,
|
||||
bboxSyncedToOptimalDimension,
|
||||
// Raster layers
|
||||
rasterLayerAdded,
|
||||
@@ -1191,13 +1241,13 @@ const syncScaledSize = (state: CanvasState) => {
|
||||
if (state.bbox.scaleMethod === 'auto') {
|
||||
// Sync both aspect ratio and size
|
||||
const { width, height } = state.bbox.rect;
|
||||
state.bbox.scaledSize = getScaledBoundingBoxDimensions({ width, height }, state.bbox.optimalDimension);
|
||||
state.bbox.scaledSize = getScaledBoundingBoxDimensions({ width, height }, state.bbox.modelBase);
|
||||
} else if (state.bbox.scaleMethod === 'manual' && state.bbox.aspectRatio.isLocked) {
|
||||
// Only sync the aspect ratio if manual & locked
|
||||
state.bbox.scaledSize = calculateNewSize(
|
||||
state.bbox.aspectRatio.value,
|
||||
state.bbox.scaledSize.width * state.bbox.scaledSize.height,
|
||||
64
|
||||
state.bbox.modelBase
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -1214,11 +1264,6 @@ export const canvasUndoableConfig: UndoableOptions<CanvasState, UnknownAction> =
|
||||
if (!action.type.startsWith(canvasSlice.name)) {
|
||||
return false;
|
||||
}
|
||||
if (bboxOptimalDimensionChanged.match(action)) {
|
||||
// This action is not triggered by the user. it's dispatched when the model is changed and will have no visible
|
||||
// effect on the canvas.
|
||||
return false;
|
||||
}
|
||||
// Throttle rapid actions of the same type
|
||||
filter = actionsThrottlingFilter(action);
|
||||
return filter;
|
||||
|
||||
@@ -15,7 +15,7 @@ import type {
|
||||
CanvasState,
|
||||
} from 'features/controlLayers/store/types';
|
||||
import { isRasterLayerEntityIdentifier } from 'features/controlLayers/store/types';
|
||||
import { getOptimalDimension } from 'features/parameters/util/optimalDimension';
|
||||
import { getGridSize, getOptimalDimension } from 'features/parameters/util/optimalDimension';
|
||||
import { assert } from 'tsafe';
|
||||
|
||||
/**
|
||||
@@ -102,10 +102,19 @@ export const selectEntityCountActive = createSelector(
|
||||
export const selectHasEntities = createSelector(selectEntityCountAll, (count) => count > 0);
|
||||
|
||||
/**
|
||||
* Selects the optimal dimension for the canvas based on the currently-model
|
||||
* Selects the optimal dimension for the canvas based on the currently-selected model
|
||||
*/
|
||||
export const selectOptimalDimension = createSelector(selectParamsSlice, (params) => {
|
||||
return getOptimalDimension(params.model);
|
||||
export const selectOptimalDimension = createSelector(selectParamsSlice, (params): number => {
|
||||
const modelBase = params.model?.base;
|
||||
return getOptimalDimension(modelBase ?? null);
|
||||
});
|
||||
|
||||
/**
|
||||
* Selects the grid size for the canvas based on the currently-selected model
|
||||
*/
|
||||
export const selectGridSize = createSelector(selectParamsSlice, (params): number => {
|
||||
const modelBase = params.model?.base;
|
||||
return getGridSize(modelBase ?? null);
|
||||
});
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { SerializableObject } from 'common/types';
|
||||
import { fetchModelConfigByIdentifier } from 'features/metadata/util/modelFetchingHelpers';
|
||||
import { zModelIdentifierField } from 'features/nodes/types/common';
|
||||
import { zMainModelBase, zModelIdentifierField } from 'features/nodes/types/common';
|
||||
import type { ParameterLoRAModel } from 'features/parameters/types/parameterSchemas';
|
||||
import {
|
||||
zParameterImageDimension,
|
||||
@@ -325,7 +325,7 @@ const zCanvasState = z.object({
|
||||
height: zParameterImageDimension,
|
||||
}),
|
||||
scaleMethod: zBoundingBoxScaleMethod,
|
||||
optimalDimension: z.number().int().positive(),
|
||||
modelBase: zMainModelBase,
|
||||
}),
|
||||
});
|
||||
export type CanvasState = z.infer<typeof zCanvasState>;
|
||||
|
||||
@@ -1,24 +1,27 @@
|
||||
import { roundToMultiple } from 'common/util/roundDownToMultiple';
|
||||
import type { Dimensions } from 'features/controlLayers/store/types';
|
||||
import type { MainModelBase } from 'features/nodes/types/common';
|
||||
import { getGridSize, getOptimalDimension } from 'features/parameters/util/optimalDimension';
|
||||
|
||||
/**
|
||||
* Scales the bounding box dimensions to the optimal dimension. The optimal dimensions should be the trained dimension
|
||||
* for the model. For example, 1024 for SDXL or 512 for SD1.5.
|
||||
* @param dimensions The un-scaled bbox dimensions
|
||||
* @param optimalDimension The optimal dimension to scale the bbox to
|
||||
* @param modelBase The base model
|
||||
*/
|
||||
export const getScaledBoundingBoxDimensions = (
|
||||
dimensions: Dimensions,
|
||||
optimalDimension: number,
|
||||
gridSize: number = 64
|
||||
): Dimensions => {
|
||||
const { width, height } = dimensions;
|
||||
export const getScaledBoundingBoxDimensions = (dimensions: Dimensions, modelBase: MainModelBase): Dimensions => {
|
||||
const optimalDimension = getOptimalDimension(modelBase);
|
||||
const gridSize = getGridSize(modelBase);
|
||||
const width = roundToMultiple(dimensions.width, gridSize);
|
||||
const height = roundToMultiple(dimensions.height, gridSize);
|
||||
|
||||
const scaledDimensions = { width, height };
|
||||
const targetArea = optimalDimension * optimalDimension;
|
||||
const aspectRatio = width / height;
|
||||
|
||||
let currentArea = width * height;
|
||||
let maxDimension = optimalDimension - gridSize;
|
||||
|
||||
while (currentArea < targetArea) {
|
||||
maxDimension += gridSize;
|
||||
if (width === height) {
|
||||
@@ -39,3 +42,21 @@ export const getScaledBoundingBoxDimensions = (
|
||||
|
||||
return scaledDimensions;
|
||||
};
|
||||
|
||||
/**
|
||||
* 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 area The input area
|
||||
* @param modelBase The base model
|
||||
* @returns The width and height that will fit the given aspect ratio, retaining the input area
|
||||
*/
|
||||
export const calculateNewSize = (ratio: number, area: number, modelBase: MainModelBase): Dimensions => {
|
||||
const exactWidth = Math.sqrt(area * ratio);
|
||||
const exactHeight = exactWidth / ratio;
|
||||
const gridSize = getGridSize(modelBase);
|
||||
|
||||
return {
|
||||
width: roundToMultiple(exactWidth, gridSize),
|
||||
height: roundToMultiple(exactHeight, gridSize),
|
||||
};
|
||||
};
|
||||
|
||||
@@ -47,7 +47,10 @@ export const MainModelDefaultSettings = memo(({ modelConfig }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const defaultSettingsDefaults = useMainModelDefaultSettings(modelConfig);
|
||||
const optimalDimension = useMemo(() => getOptimalDimension(modelConfig), [modelConfig]);
|
||||
const optimalDimension = useMemo(() => {
|
||||
const modelBase = modelConfig?.base;
|
||||
return getOptimalDimension(modelBase ?? null);
|
||||
}, [modelConfig]);
|
||||
const [updateModel, { isLoading: isLoadingUpdateModel }] = useUpdateModelMutation();
|
||||
|
||||
const { handleSubmit, control, formState, reset } = useForm<MainModelDefaultSettingsFormData>({
|
||||
|
||||
@@ -62,6 +62,9 @@ export type SchedulerField = z.infer<typeof zSchedulerField>;
|
||||
|
||||
// #region Model-related schemas
|
||||
const zBaseModel = z.enum(['any', 'sd-1', 'sd-2', 'sdxl', 'sdxl-refiner', 'flux']);
|
||||
export const zMainModelBase = z.enum(['sd-1', 'sd-2', 'sdxl', 'flux']);
|
||||
export type MainModelBase = z.infer<typeof zMainModelBase>;
|
||||
export const isMainModelBase = (base: unknown): base is MainModelBase => zMainModelBase.safeParse(base).success;
|
||||
const zModelType = z.enum([
|
||||
'main',
|
||||
'vae',
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
|
||||
import { bboxHeightChanged } from 'features/controlLayers/store/canvasSlice';
|
||||
import { selectIsStaging } from 'features/controlLayers/store/canvasStagingAreaSlice';
|
||||
import { selectHeight, selectOptimalDimension } from 'features/controlLayers/store/selectors';
|
||||
import { selectGridSize, selectHeight, selectOptimalDimension } from 'features/controlLayers/store/selectors';
|
||||
import { selectHeightConfig } from 'features/system/store/configSlice';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@@ -15,6 +15,7 @@ export const BboxHeight = memo(() => {
|
||||
const height = useAppSelector(selectHeight);
|
||||
const config = useAppSelector(selectHeightConfig);
|
||||
const isStaging = useAppSelector(selectIsStaging);
|
||||
const gridSize = useAppSelector(selectGridSize);
|
||||
|
||||
const onChange = useCallback(
|
||||
(v: number) => {
|
||||
@@ -40,7 +41,7 @@ export const BboxHeight = memo(() => {
|
||||
min={config.sliderMin}
|
||||
max={config.sliderMax}
|
||||
step={config.coarseStep}
|
||||
fineStep={config.fineStep}
|
||||
fineStep={gridSize}
|
||||
marks={marks}
|
||||
/>
|
||||
<CompositeNumberInput
|
||||
@@ -50,7 +51,7 @@ export const BboxHeight = memo(() => {
|
||||
min={config.numberInputMin}
|
||||
max={config.numberInputMax}
|
||||
step={config.coarseStep}
|
||||
fineStep={config.fineStep}
|
||||
fineStep={gridSize}
|
||||
/>
|
||||
</FormControl>
|
||||
);
|
||||
|
||||
@@ -3,7 +3,7 @@ import { createSelector } from '@reduxjs/toolkit';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { bboxScaledHeightChanged } from 'features/controlLayers/store/canvasSlice';
|
||||
import { selectIsStaging } from 'features/controlLayers/store/canvasStagingAreaSlice';
|
||||
import { selectCanvasSlice, selectOptimalDimension } from 'features/controlLayers/store/selectors';
|
||||
import { selectCanvasSlice, selectGridSize, selectOptimalDimension } from 'features/controlLayers/store/selectors';
|
||||
import { selectConfigSlice } from 'features/system/store/configSlice';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@@ -23,6 +23,7 @@ const BboxScaledHeight = () => {
|
||||
const isManual = useAppSelector(selectIsManual);
|
||||
const scaledHeight = useAppSelector(selectScaledHeight);
|
||||
const config = useAppSelector(selectScaledBoundingBoxHeightConfig);
|
||||
const gridSize = useAppSelector(selectGridSize);
|
||||
|
||||
const onChange = useCallback(
|
||||
(height: number) => {
|
||||
@@ -38,7 +39,7 @@ const BboxScaledHeight = () => {
|
||||
min={config.sliderMin}
|
||||
max={config.sliderMax}
|
||||
step={config.coarseStep}
|
||||
fineStep={config.fineStep}
|
||||
fineStep={gridSize}
|
||||
value={scaledHeight}
|
||||
onChange={onChange}
|
||||
marks
|
||||
@@ -48,7 +49,7 @@ const BboxScaledHeight = () => {
|
||||
min={config.numberInputMin}
|
||||
max={config.numberInputMax}
|
||||
step={config.coarseStep}
|
||||
fineStep={config.fineStep}
|
||||
fineStep={gridSize}
|
||||
value={scaledHeight}
|
||||
onChange={onChange}
|
||||
defaultValue={optimalDimension}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { createSelector } from '@reduxjs/toolkit';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { bboxScaledWidthChanged } from 'features/controlLayers/store/canvasSlice';
|
||||
import { selectIsStaging } from 'features/controlLayers/store/canvasStagingAreaSlice';
|
||||
import { selectCanvasSlice, selectOptimalDimension } from 'features/controlLayers/store/selectors';
|
||||
import { selectCanvasSlice, selectGridSize, selectOptimalDimension } from 'features/controlLayers/store/selectors';
|
||||
import { selectConfigSlice } from 'features/system/store/configSlice';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@@ -23,6 +23,8 @@ const BboxScaledWidth = () => {
|
||||
const isManual = useAppSelector(selectIsManual);
|
||||
const scaledWidth = useAppSelector(selectScaledWidth);
|
||||
const config = useAppSelector(selectScaledBoundingBoxWidthConfig);
|
||||
const gridSize = useAppSelector(selectGridSize);
|
||||
|
||||
const onChange = useCallback(
|
||||
(width: number) => {
|
||||
dispatch(bboxScaledWidthChanged(width));
|
||||
@@ -37,7 +39,7 @@ const BboxScaledWidth = () => {
|
||||
min={config.sliderMin}
|
||||
max={config.sliderMax}
|
||||
step={config.coarseStep}
|
||||
fineStep={config.fineStep}
|
||||
fineStep={gridSize}
|
||||
value={scaledWidth}
|
||||
onChange={onChange}
|
||||
defaultValue={optimalDimension}
|
||||
@@ -47,7 +49,7 @@ const BboxScaledWidth = () => {
|
||||
min={config.numberInputMin}
|
||||
max={config.numberInputMax}
|
||||
step={config.coarseStep}
|
||||
fineStep={config.fineStep}
|
||||
fineStep={gridSize}
|
||||
value={scaledWidth}
|
||||
onChange={onChange}
|
||||
defaultValue={optimalDimension}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
|
||||
import { bboxWidthChanged } from 'features/controlLayers/store/canvasSlice';
|
||||
import { selectIsStaging } from 'features/controlLayers/store/canvasStagingAreaSlice';
|
||||
import { selectOptimalDimension, selectWidth } from 'features/controlLayers/store/selectors';
|
||||
import { selectGridSize, selectOptimalDimension, selectWidth } from 'features/controlLayers/store/selectors';
|
||||
import { selectWidthConfig } from 'features/system/store/configSlice';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@@ -15,6 +15,7 @@ export const BboxWidth = memo(() => {
|
||||
const optimalDimension = useAppSelector(selectOptimalDimension);
|
||||
const config = useAppSelector(selectWidthConfig);
|
||||
const isStaging = useAppSelector(selectIsStaging);
|
||||
const gridSize = useAppSelector(selectGridSize);
|
||||
|
||||
const onChange = useCallback(
|
||||
(v: number) => {
|
||||
@@ -40,7 +41,7 @@ export const BboxWidth = memo(() => {
|
||||
min={config.sliderMin}
|
||||
max={config.sliderMax}
|
||||
step={config.coarseStep}
|
||||
fineStep={config.fineStep}
|
||||
fineStep={gridSize}
|
||||
marks={marks}
|
||||
/>
|
||||
<CompositeNumberInput
|
||||
@@ -50,7 +51,7 @@ export const BboxWidth = memo(() => {
|
||||
min={config.numberInputMin}
|
||||
max={config.numberInputMax}
|
||||
step={config.coarseStep}
|
||||
fineStep={config.fineStep}
|
||||
fineStep={gridSize}
|
||||
/>
|
||||
</FormControl>
|
||||
);
|
||||
|
||||
@@ -1,21 +1,2 @@
|
||||
import { roundToMultiple } from 'common/util/roundDownToMultiple';
|
||||
|
||||
/**
|
||||
* 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 area The input area
|
||||
* @returns The width and height that will fit the given aspect ratio, retaining the input area
|
||||
*/
|
||||
export const calculateNewSize = (
|
||||
ratio: number,
|
||||
area: number,
|
||||
gridSize: number = 8
|
||||
): { width: number; height: number } => {
|
||||
const exactWidth = Math.sqrt(area * ratio);
|
||||
const exactHeight = exactWidth / ratio;
|
||||
|
||||
return {
|
||||
width: roundToMultiple(exactWidth, gridSize),
|
||||
height: roundToMultiple(exactHeight, gridSize),
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,12 +1,45 @@
|
||||
import type { ModelIdentifierField } from 'features/nodes/types/common';
|
||||
import type { MainModelBase } from 'features/nodes/types/common';
|
||||
import type { BaseModelType } from 'services/api/types';
|
||||
|
||||
/**
|
||||
* 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
|
||||
* Gets the optimal dimension for a given base model:
|
||||
* - sd-1, sd-2: 512
|
||||
* - sdxl, flux: 1024
|
||||
* - default: 1024
|
||||
* @param base The base model
|
||||
* @returns The optimal dimension for the model, defaulting to 512
|
||||
*/
|
||||
export const getOptimalDimension = (model?: ModelIdentifierField | null): number =>
|
||||
model?.base === 'sdxl' || model?.base === 'flux' ? 1024 : 512;
|
||||
export const getOptimalDimension = (base?: BaseModelType | null): number => {
|
||||
switch (base) {
|
||||
case 'sd-1':
|
||||
case 'sd-2':
|
||||
return 512;
|
||||
case 'sdxl':
|
||||
case 'flux':
|
||||
default:
|
||||
return 1024;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the grid size for a given base model. For Flux, the grid size is 16, otherwise it is 8.
|
||||
* - sd-1, sd-2, sdxl: 8
|
||||
* - flux: 16
|
||||
* - default: 8
|
||||
* @param base The base model
|
||||
* @returns The grid size for the model, defaulting to 8
|
||||
*/
|
||||
export const getGridSize = (base?: BaseModelType | null): number => {
|
||||
switch (base) {
|
||||
case 'flux':
|
||||
return 16;
|
||||
case 'sd-1':
|
||||
case 'sd-2':
|
||||
case 'sdxl':
|
||||
default:
|
||||
return 8;
|
||||
}
|
||||
};
|
||||
|
||||
const MIN_AREA_FACTOR = 0.8;
|
||||
const MAX_AREA_FACTOR = 1.2;
|
||||
@@ -37,6 +70,7 @@ export const getIsSizeTooLarge = (width: number, height: number, optimalDimensio
|
||||
* @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 => {
|
||||
export const getIsSizeOptimal = (width: number, height: number, modelBase: MainModelBase): boolean => {
|
||||
const optimalDimension = getOptimalDimension(modelBase);
|
||||
return !getIsSizeTooSmall(width, height, optimalDimension) && !getIsSizeTooLarge(width, height, optimalDimension);
|
||||
};
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { Expander, Flex, StandaloneAccordion } from '@invoke-ai/ui-library';
|
||||
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { roundDownToMultiple } from 'common/util/roundDownToMultiple';
|
||||
import ParamCreativity from 'features/parameters/components/Upscale/ParamCreativity';
|
||||
import ParamSpandrelModel from 'features/parameters/components/Upscale/ParamSpandrelModel';
|
||||
import ParamStructure from 'features/parameters/components/Upscale/ParamStructure';
|
||||
import { selectUpscaleSlice } from 'features/parameters/store/upscaleSlice';
|
||||
import { getGridSize } from 'features/parameters/util/optimalDimension';
|
||||
import { UpscaleScaleSlider } from 'features/settingsAccordions/components/UpscaleSettingsAccordion/UpscaleScaleSlider';
|
||||
import { useExpanderToggle } from 'features/settingsAccordions/hooks/useExpanderToggle';
|
||||
import { useStandaloneAccordionToggle } from 'features/settingsAccordions/hooks/useStandaloneAccordionToggle';
|
||||
@@ -24,9 +26,10 @@ const selector = createMemoizedSelector([selectUpscaleSlice], (upscaleSlice) =>
|
||||
}
|
||||
|
||||
if (upscaleInitialImage) {
|
||||
const gridSize = upscaleModel ? getGridSize(upscaleModel.base) : getGridSize(null);
|
||||
// Output height and width are scaled and rounded down to the nearest multiple of 8
|
||||
const outputWidth = Math.floor((upscaleInitialImage.width * scale) / 8) * 8;
|
||||
const outputHeight = Math.floor((upscaleInitialImage.height * scale) / 8) * 8;
|
||||
const outputWidth = roundDownToMultiple(upscaleInitialImage.width * scale, gridSize);
|
||||
const outputHeight = roundDownToMultiple(upscaleInitialImage.height * scale, gridSize);
|
||||
|
||||
badges.push(`${outputWidth}×${outputHeight}`);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user