Skip to content

Commit f002ae8

Browse files
feat(ui): max upscale pixels config (#4765)
* feat(ui): max upscale pixels config Add `maxUpscalePixels: number` to the app config. The number should be the *total* number of pixels eg `maxUpscalePixels: 4096 * 4096`. If not provided, any size image may be upscaled. If the config is provided, users will see be advised if their image is too large for either model, or told to switch to an x2 model if it's only too large for x4. The message is via tooltip in the popover and via toast if the user uses the hotkey to upscale. * feat(ui): "mayUpscale" -> "isAllowedToUpscale"
1 parent 208bf68 commit f002ae8

File tree

7 files changed

+139
-9
lines changed

7 files changed

+139
-9
lines changed

invokeai/frontend/web/public/locales/en.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1015,7 +1015,11 @@
10151015
"variationAmount": "Variation Amount",
10161016
"variations": "Variations",
10171017
"vSymmetryStep": "V Symmetry Step",
1018-
"width": "Width"
1018+
"width": "Width",
1019+
"isAllowedToUpscale": {
1020+
"useX2Model": "Image is too large to upscale with x4 model, use x2 model",
1021+
"tooLarge": "Image is too large to upscale, select smaller image"
1022+
}
10191023
},
10201024
"dynamicPrompts": {
10211025
"combinatorial": "Combinatorial Generation",

invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/upscaleRequested.ts

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@ import { addToast } from 'features/system/store/systemSlice';
66
import { t } from 'i18next';
77
import { queueApi } from 'services/api/endpoints/queue';
88
import { startAppListening } from '..';
9+
import { ImageDTO } from 'services/api/types';
10+
import { createIsAllowedToUpscaleSelector } from 'features/parameters/hooks/useIsAllowedToUpscale';
911

10-
export const upscaleRequested = createAction<{ image_name: string }>(
12+
export const upscaleRequested = createAction<{ imageDTO: ImageDTO }>(
1113
`upscale/upscaleRequested`
1214
);
1315

@@ -17,8 +19,28 @@ export const addUpscaleRequestedListener = () => {
1719
effect: async (action, { dispatch, getState }) => {
1820
const log = logger('session');
1921

20-
const { image_name } = action.payload;
22+
const { imageDTO } = action.payload;
23+
const { image_name } = imageDTO;
2124
const state = getState();
25+
26+
const { isAllowedToUpscale, detailTKey } =
27+
createIsAllowedToUpscaleSelector(imageDTO)(state);
28+
29+
// if we can't upscale, show a toast and return
30+
if (!isAllowedToUpscale) {
31+
log.error(
32+
{ imageDTO },
33+
t(detailTKey ?? 'parameters.isAllowedToUpscale.tooLarge') // should never coalesce
34+
);
35+
dispatch(
36+
addToast({
37+
title: t(detailTKey ?? 'parameters.isAllowedToUpscale.tooLarge'), // should never coalesce
38+
status: 'error',
39+
})
40+
);
41+
return;
42+
}
43+
2244
const { esrganModelName } = state.postprocessing;
2345
const { autoAddBoardId } = state.gallery;
2446

invokeai/frontend/web/src/app/types/invokeai.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ export type AppConfig = {
5656
canRestoreDeletedImagesFromBin: boolean;
5757
nodesAllowlist: string[] | undefined;
5858
nodesDenylist: string[] | undefined;
59+
maxUpscalePixels?: number;
5960
sd: {
6061
defaultModel?: string;
6162
disabledControlNetModels: string[];

invokeai/frontend/web/src/features/gallery/components/CurrentImage/CurrentImageButtons.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
181181
if (!imageDTO) {
182182
return;
183183
}
184-
dispatch(upscaleRequested({ image_name: imageDTO.image_name }));
184+
dispatch(upscaleRequested({ imageDTO }));
185185
}, [dispatch, imageDTO]);
186186

187187
const handleDelete = useCallback(() => {

invokeai/frontend/web/src/features/nodes/components/flow/Flow.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ import {
3030
connectionEnded,
3131
connectionMade,
3232
connectionStarted,
33-
edgeChangeStarted,
3433
edgeAdded,
34+
edgeChangeStarted,
3535
edgeDeleted,
3636
edgesChanged,
3737
edgesDeleted,

invokeai/frontend/web/src/features/parameters/components/Parameters/Upscale/ParamUpscaleSettings.tsx

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { useAppDispatch } from 'app/store/storeHooks';
44
import IAIButton from 'common/components/IAIButton';
55
import IAIIconButton from 'common/components/IAIIconButton';
66
import IAIPopover from 'common/components/IAIPopover';
7+
import { useIsAllowedToUpscale } from 'features/parameters/hooks/useIsAllowedToUpscale';
78
import { useIsQueueMutationInProgress } from 'features/queue/hooks/useIsQueueMutationInProgress';
89
import { memo, useCallback } from 'react';
910
import { useTranslation } from 'react-i18next';
@@ -19,14 +20,15 @@ const ParamUpscalePopover = (props: Props) => {
1920
const inProgress = useIsQueueMutationInProgress();
2021
const { t } = useTranslation();
2122
const { isOpen, onOpen, onClose } = useDisclosure();
23+
const { isAllowedToUpscale, detail } = useIsAllowedToUpscale(imageDTO);
2224

2325
const handleClickUpscale = useCallback(() => {
2426
onClose();
25-
if (!imageDTO) {
27+
if (!imageDTO || !isAllowedToUpscale) {
2628
return;
2729
}
28-
dispatch(upscaleRequested({ image_name: imageDTO.image_name }));
29-
}, [dispatch, imageDTO, onClose]);
30+
dispatch(upscaleRequested({ imageDTO }));
31+
}, [dispatch, imageDTO, isAllowedToUpscale, onClose]);
3032

3133
return (
3234
<IAIPopover
@@ -49,8 +51,9 @@ const ParamUpscalePopover = (props: Props) => {
4951
>
5052
<ParamESRGANModel />
5153
<IAIButton
54+
tooltip={detail}
5255
size="sm"
53-
isDisabled={!imageDTO || inProgress}
56+
isDisabled={!imageDTO || inProgress || !isAllowedToUpscale}
5457
onClick={handleClickUpscale}
5558
>
5659
{t('parameters.upscaleImage')}
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import { createSelector } from '@reduxjs/toolkit';
2+
import { stateSelector } from 'app/store/store';
3+
import { useAppSelector } from 'app/store/storeHooks';
4+
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
5+
import { useMemo } from 'react';
6+
import { useTranslation } from 'react-i18next';
7+
import { ImageDTO } from 'services/api/types';
8+
9+
const getUpscaledPixels = (imageDTO?: ImageDTO, maxUpscalePixels?: number) => {
10+
if (!imageDTO) {
11+
return;
12+
}
13+
if (!maxUpscalePixels) {
14+
return;
15+
}
16+
const { width, height } = imageDTO;
17+
const x4 = height * 4 * width * 4;
18+
const x2 = height * 2 * width * 2;
19+
return { x4, x2 };
20+
};
21+
22+
const getIsAllowedToUpscale = (
23+
upscaledPixels?: ReturnType<typeof getUpscaledPixels>,
24+
maxUpscalePixels?: number
25+
) => {
26+
if (!upscaledPixels || !maxUpscalePixels) {
27+
return { x4: true, x2: true };
28+
}
29+
const isAllowedToUpscale = { x4: false, x2: false };
30+
if (upscaledPixels.x4 <= maxUpscalePixels) {
31+
isAllowedToUpscale.x4 = true;
32+
}
33+
if (upscaledPixels.x2 <= maxUpscalePixels) {
34+
isAllowedToUpscale.x2 = true;
35+
}
36+
37+
return isAllowedToUpscale;
38+
};
39+
40+
const getDetailTKey = (
41+
isAllowedToUpscale?: ReturnType<typeof getIsAllowedToUpscale>,
42+
scaleFactor?: number
43+
) => {
44+
if (!isAllowedToUpscale || !scaleFactor) {
45+
return;
46+
}
47+
48+
if (isAllowedToUpscale.x4 && isAllowedToUpscale.x2) {
49+
return;
50+
}
51+
52+
if (!isAllowedToUpscale.x2 && !isAllowedToUpscale.x4) {
53+
return 'parameters.isAllowedToUpscale.tooLarge';
54+
}
55+
56+
if (!isAllowedToUpscale.x4 && isAllowedToUpscale.x2 && scaleFactor === 4) {
57+
return 'parameters.isAllowedToUpscale.useX2Model';
58+
}
59+
60+
return;
61+
};
62+
63+
export const createIsAllowedToUpscaleSelector = (imageDTO?: ImageDTO) =>
64+
createSelector(
65+
stateSelector,
66+
({ postprocessing, config }) => {
67+
const { esrganModelName } = postprocessing;
68+
const { maxUpscalePixels } = config;
69+
70+
const upscaledPixels = getUpscaledPixels(imageDTO, maxUpscalePixels);
71+
const isAllowedToUpscale = getIsAllowedToUpscale(
72+
upscaledPixels,
73+
maxUpscalePixels
74+
);
75+
const scaleFactor = esrganModelName.includes('x2') ? 2 : 4;
76+
const detailTKey = getDetailTKey(isAllowedToUpscale, scaleFactor);
77+
return {
78+
isAllowedToUpscale:
79+
scaleFactor === 2 ? isAllowedToUpscale.x2 : isAllowedToUpscale.x4,
80+
detailTKey,
81+
};
82+
},
83+
defaultSelectorOptions
84+
);
85+
86+
export const useIsAllowedToUpscale = (imageDTO?: ImageDTO) => {
87+
const { t } = useTranslation();
88+
const selectIsAllowedToUpscale = useMemo(
89+
() => createIsAllowedToUpscaleSelector(imageDTO),
90+
[imageDTO]
91+
);
92+
const { isAllowedToUpscale, detailTKey } = useAppSelector(
93+
selectIsAllowedToUpscale
94+
);
95+
96+
return {
97+
isAllowedToUpscale,
98+
detail: detailTKey ? t(detailTKey) : undefined,
99+
};
100+
};

0 commit comments

Comments
 (0)