Skip to content

Commit ffff2d6

Browse files
feat(ui): add New from Image submenu for image ctx menu
1 parent afa9f07 commit ffff2d6

File tree

5 files changed

+180
-84
lines changed

5 files changed

+180
-84
lines changed

invokeai/frontend/web/src/features/controlLayers/hooks/addLayerHooks.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@ import {
2424
selectEntityOrThrow,
2525
} from 'features/controlLayers/store/selectors';
2626
import type {
27+
CanvasControlLayerState,
2728
CanvasEntityIdentifier,
29+
CanvasInpaintMaskState,
2830
CanvasRasterLayerState,
2931
CanvasRegionalGuidanceState,
3032
ControlNetConfig,
@@ -124,6 +126,60 @@ export const useNewRasterLayerFromImage = () => {
124126
return func;
125127
};
126128

129+
export const useNewControlLayerFromImage = () => {
130+
const dispatch = useAppDispatch();
131+
const bboxRect = useAppSelector(selectBboxRect);
132+
const func = useCallback(
133+
(imageDTO: ImageDTO) => {
134+
const imageObject = imageDTOToImageObject(imageDTO);
135+
const overrides: Partial<CanvasControlLayerState> = {
136+
position: { x: bboxRect.x, y: bboxRect.y },
137+
objects: [imageObject],
138+
};
139+
dispatch(controlLayerAdded({ overrides, isSelected: true }));
140+
},
141+
[bboxRect.x, bboxRect.y, dispatch]
142+
);
143+
144+
return func;
145+
};
146+
147+
export const useNewInpaintMaskFromImage = () => {
148+
const dispatch = useAppDispatch();
149+
const bboxRect = useAppSelector(selectBboxRect);
150+
const func = useCallback(
151+
(imageDTO: ImageDTO) => {
152+
const imageObject = imageDTOToImageObject(imageDTO);
153+
const overrides: Partial<CanvasInpaintMaskState> = {
154+
position: { x: bboxRect.x, y: bboxRect.y },
155+
objects: [imageObject],
156+
};
157+
dispatch(inpaintMaskAdded({ overrides, isSelected: true }));
158+
},
159+
[bboxRect.x, bboxRect.y, dispatch]
160+
);
161+
162+
return func;
163+
};
164+
165+
export const useNewRegionalGuidanceFromImage = () => {
166+
const dispatch = useAppDispatch();
167+
const bboxRect = useAppSelector(selectBboxRect);
168+
const func = useCallback(
169+
(imageDTO: ImageDTO) => {
170+
const imageObject = imageDTOToImageObject(imageDTO);
171+
const overrides: Partial<CanvasRegionalGuidanceState> = {
172+
position: { x: bboxRect.x, y: bboxRect.y },
173+
objects: [imageObject],
174+
};
175+
dispatch(rgAdded({ overrides, isSelected: true }));
176+
},
177+
[bboxRect.x, bboxRect.y, dispatch]
178+
);
179+
180+
return func;
181+
};
182+
127183
/**
128184
* Returns a function that adds a new canvas with the given image as the initial image, replicating the img2img flow:
129185
* - Reset the canvas

invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/ImageMenuItemNewCanvasFromImage.tsx

Lines changed: 0 additions & 39 deletions
This file was deleted.
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
import { Menu, MenuButton, MenuItem, MenuList } from '@invoke-ai/ui-library';
2+
import { useAppDispatch } from 'app/store/storeHooks';
3+
import { SubMenuButtonContent, useSubMenu } from 'common/hooks/useSubMenu';
4+
import { NewLayerIcon } from 'features/controlLayers/components/common/icons';
5+
import {
6+
useNewCanvasFromImage,
7+
useNewControlLayerFromImage,
8+
useNewInpaintMaskFromImage,
9+
useNewRasterLayerFromImage,
10+
useNewRegionalGuidanceFromImage,
11+
} from 'features/controlLayers/hooks/addLayerHooks';
12+
import { useCanvasIsBusy } from 'features/controlLayers/hooks/useCanvasIsBusy';
13+
import { useImageViewer } from 'features/gallery/components/ImageViewer/useImageViewer';
14+
import { useImageDTOContext } from 'features/gallery/contexts/ImageDTOContext';
15+
import { sentImageToCanvas } from 'features/gallery/store/actions';
16+
import { toast } from 'features/toast/toast';
17+
import { setActiveTab } from 'features/ui/store/uiSlice';
18+
import { memo, useCallback } from 'react';
19+
import { useTranslation } from 'react-i18next';
20+
import { PiFileBold, PiPlusBold } from 'react-icons/pi';
21+
22+
export const ImageMenuItemNewFromImageSubMenu = memo(() => {
23+
const { t } = useTranslation();
24+
const subMenu = useSubMenu();
25+
const dispatch = useAppDispatch();
26+
const imageDTO = useImageDTOContext();
27+
const imageViewer = useImageViewer();
28+
const isBusy = useCanvasIsBusy();
29+
const newRasterLayerFromImage = useNewRasterLayerFromImage();
30+
const newControlLayerFromImage = useNewControlLayerFromImage();
31+
const newInpaintMaskFromImage = useNewInpaintMaskFromImage();
32+
const newRegionalGuidanceFromImage = useNewRegionalGuidanceFromImage();
33+
const newCanvasFromImage = useNewCanvasFromImage();
34+
35+
const onClickNewCanvasFromImage = useCallback(() => {
36+
newCanvasFromImage(imageDTO);
37+
dispatch(setActiveTab('canvas'));
38+
imageViewer.close();
39+
toast({
40+
id: 'SENT_TO_CANVAS',
41+
title: t('toast.sentToCanvas'),
42+
status: 'success',
43+
});
44+
}, [dispatch, imageDTO, imageViewer, newCanvasFromImage, t]);
45+
46+
const onClickNewRasterLayerFromImage = useCallback(() => {
47+
dispatch(sentImageToCanvas());
48+
newRasterLayerFromImage(imageDTO);
49+
dispatch(setActiveTab('canvas'));
50+
imageViewer.close();
51+
toast({
52+
id: 'SENT_TO_CANVAS',
53+
title: t('toast.sentToCanvas'),
54+
status: 'success',
55+
});
56+
}, [dispatch, imageDTO, imageViewer, newRasterLayerFromImage, t]);
57+
58+
const onClickNewControlLayerFromImage = useCallback(() => {
59+
dispatch(sentImageToCanvas());
60+
newControlLayerFromImage(imageDTO);
61+
dispatch(setActiveTab('canvas'));
62+
imageViewer.close();
63+
toast({
64+
id: 'SENT_TO_CANVAS',
65+
title: t('toast.sentToCanvas'),
66+
status: 'success',
67+
});
68+
}, [dispatch, imageDTO, imageViewer, newControlLayerFromImage, t]);
69+
70+
const onClickNewInpaintMaskFromImage = useCallback(() => {
71+
dispatch(sentImageToCanvas());
72+
newInpaintMaskFromImage(imageDTO);
73+
dispatch(setActiveTab('canvas'));
74+
imageViewer.close();
75+
toast({
76+
id: 'SENT_TO_CANVAS',
77+
title: t('toast.sentToCanvas'),
78+
status: 'success',
79+
});
80+
}, [dispatch, imageDTO, imageViewer, newInpaintMaskFromImage, t]);
81+
82+
const onClickNewRegionalGuidanceFromImage = useCallback(() => {
83+
dispatch(sentImageToCanvas());
84+
newRegionalGuidanceFromImage(imageDTO);
85+
dispatch(setActiveTab('canvas'));
86+
imageViewer.close();
87+
toast({
88+
id: 'SENT_TO_CANVAS',
89+
title: t('toast.sentToCanvas'),
90+
status: 'success',
91+
});
92+
}, [dispatch, imageDTO, imageViewer, newRegionalGuidanceFromImage, t]);
93+
94+
return (
95+
<MenuItem {...subMenu.parentMenuItemProps} icon={<PiPlusBold />}>
96+
<Menu {...subMenu.menuProps}>
97+
<MenuButton {...subMenu.menuButtonProps}>
98+
<SubMenuButtonContent label="New from Image" />
99+
</MenuButton>
100+
<MenuList {...subMenu.menuListProps}>
101+
<MenuItem icon={<PiFileBold />} onClickCapture={onClickNewCanvasFromImage} isDisabled={isBusy}>
102+
{t('controlLayers.canvas')}
103+
</MenuItem>
104+
<MenuItem icon={<NewLayerIcon />} onClickCapture={onClickNewInpaintMaskFromImage} isDisabled={isBusy}>
105+
{t('controlLayers.inpaintMask')}
106+
</MenuItem>
107+
<MenuItem icon={<NewLayerIcon />} onClickCapture={onClickNewRegionalGuidanceFromImage} isDisabled={isBusy}>
108+
{t('controlLayers.regionalGuidance')}
109+
</MenuItem>
110+
<MenuItem icon={<NewLayerIcon />} onClickCapture={onClickNewControlLayerFromImage} isDisabled={isBusy}>
111+
{t('controlLayers.controlLayer')}
112+
</MenuItem>
113+
<MenuItem icon={<NewLayerIcon />} onClickCapture={onClickNewRasterLayerFromImage} isDisabled={isBusy}>
114+
{t('controlLayers.rasterLayer')}
115+
</MenuItem>
116+
</MenuList>
117+
</Menu>
118+
</MenuItem>
119+
);
120+
});
121+
122+
ImageMenuItemNewFromImageSubMenu.displayName = 'ImageMenuItemNewFromImageSubMenu';

invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/ImageMenuItemNewLayerFromImage.tsx

Lines changed: 0 additions & 41 deletions
This file was deleted.

invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/SingleSelectionMenuItems.tsx

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,7 @@ import { ImageMenuItemDelete } from 'features/gallery/components/ImageContextMen
77
import { ImageMenuItemDownload } from 'features/gallery/components/ImageContextMenu/ImageMenuItemDownload';
88
import { ImageMenuItemLoadWorkflow } from 'features/gallery/components/ImageContextMenu/ImageMenuItemLoadWorkflow';
99
import { ImageMenuItemMetadataRecallActions } from 'features/gallery/components/ImageContextMenu/ImageMenuItemMetadataRecallActions';
10-
import { ImageMenuItemNewCanvasFromImage } from 'features/gallery/components/ImageContextMenu/ImageMenuItemNewCanvasFromImage';
11-
import { ImageMenuItemNewLayerFromImage } from 'features/gallery/components/ImageContextMenu/ImageMenuItemNewLayerFromImage';
10+
import { ImageMenuItemNewFromImageSubMenu } from 'features/gallery/components/ImageContextMenu/ImageMenuItemNewFromImageSubMenu';
1211
import { ImageMenuItemOpenInNewTab } from 'features/gallery/components/ImageContextMenu/ImageMenuItemOpenInNewTab';
1312
import { ImageMenuItemOpenInViewer } from 'features/gallery/components/ImageContextMenu/ImageMenuItemOpenInViewer';
1413
import { ImageMenuItemSelectForCompare } from 'features/gallery/components/ImageContextMenu/ImageMenuItemSelectForCompare';
@@ -39,8 +38,7 @@ const SingleSelectionMenuItems = ({ imageDTO }: SingleSelectionMenuItemsProps) =
3938
<MenuDivider />
4039
<ImageMenuItemSendToUpscale />
4140
<CanvasManagerProviderGate>
42-
<ImageMenuItemNewLayerFromImage />
43-
<ImageMenuItemNewCanvasFromImage />
41+
<ImageMenuItemNewFromImageSubMenu />
4442
</CanvasManagerProviderGate>
4543
<MenuDivider />
4644
<ImageMenuItemChangeBoard />

0 commit comments

Comments
 (0)