Skip to content

Commit 88dcb38

Browse files
psychedelicioushipsterusername
authored andcommitted
feat(ui): pull bbox into functionality for control/ip adapters
1 parent 5a89bf8 commit 88dcb38

File tree

5 files changed

+116
-8
lines changed

5 files changed

+116
-8
lines changed

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1782,6 +1782,8 @@
17821782
"flipVertical": "Flip Vertical",
17831783
"stagingOnCanvas": "Staging images on",
17841784
"replaceLayer": "Replace Layer",
1785+
"pullBboxIntoLayer": "Pull Bbox into Layer",
1786+
"pullBboxIntoIPAdapter": "Pull Bbox into IP Adapter",
17851787
"fill": {
17861788
"fillColor": "Fill Color",
17871789
"fillStyle": "Fill Style",

invokeai/frontend/web/src/features/controlLayers/components/ControlLayer/ControlLayerControlAdapter.tsx

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { Weight } from 'features/controlLayers/components/common/Weight';
66
import { ControlLayerControlAdapterControlMode } from 'features/controlLayers/components/ControlLayer/ControlLayerControlAdapterControlMode';
77
import { ControlLayerControlAdapterModel } from 'features/controlLayers/components/ControlLayer/ControlLayerControlAdapterModel';
88
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
9+
import { useIsSavingCanvas, usePullBboxIntoLayer } from 'features/controlLayers/hooks/saveCanvasHooks';
910
import { useEntityFilter } from 'features/controlLayers/hooks/useEntityFilter';
1011
import {
1112
controlLayerBeginEndStepPctChanged,
@@ -17,7 +18,7 @@ import { selectCanvasSlice, selectEntityOrThrow } from 'features/controlLayers/s
1718
import type { CanvasEntityIdentifier, ControlModeV2 } from 'features/controlLayers/store/types';
1819
import { memo, useCallback, useMemo } from 'react';
1920
import { useTranslation } from 'react-i18next';
20-
import { PiShootingStarBold } from 'react-icons/pi';
21+
import { PiBoundingBoxBold, PiShootingStarBold } from 'react-icons/pi';
2122
import type { ControlNetModelConfig, T2IAdapterModelConfig } from 'services/api/types';
2223

2324
const useControlLayerControlAdapter = (entityIdentifier: CanvasEntityIdentifier<'control_layer'>) => {
@@ -68,6 +69,9 @@ export const ControlLayerControlAdapter = memo(() => {
6869
[dispatch, entityIdentifier]
6970
);
7071

72+
const pullBboxIntoLayer = usePullBboxIntoLayer(entityIdentifier);
73+
const isSaving = useIsSavingCanvas();
74+
7175
return (
7276
<Flex flexDir="column" gap={3} position="relative" w="full">
7377
<Flex w="full" gap={2}>
@@ -80,6 +84,14 @@ export const ControlLayerControlAdapter = memo(() => {
8084
tooltip={t('controlLayers.filter.filter')}
8185
icon={<PiShootingStarBold />}
8286
/>
87+
<IconButton
88+
onClick={pullBboxIntoLayer}
89+
isLoading={isSaving.isTrue}
90+
variant="ghost"
91+
aria-label={t('controlLayers.pullBboxIntoLayer')}
92+
tooltip={t('controlLayers.pullBboxIntoLayer')}
93+
icon={<PiBoundingBoxBold />}
94+
/>
8395
</Flex>
8496
<Weight weight={controlAdapter.weight} onChange={onChangeWeight} />
8597
<BeginEndStepPct beginEndStepPct={controlAdapter.beginEndStepPct} onChange={onChangeBeginEndStepPct} />

invokeai/frontend/web/src/features/controlLayers/components/IPAdapter/IPAdapterSettings.tsx

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
import { Box, Flex } from '@invoke-ai/ui-library';
1+
import { Box, Flex, IconButton } from '@invoke-ai/ui-library';
22
import { createSelector } from '@reduxjs/toolkit';
33
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
44
import { BeginEndStepPct } from 'features/controlLayers/components/common/BeginEndStepPct';
55
import { CanvasEntitySettingsWrapper } from 'features/controlLayers/components/common/CanvasEntitySettingsWrapper';
66
import { Weight } from 'features/controlLayers/components/common/Weight';
77
import { IPAdapterMethod } from 'features/controlLayers/components/IPAdapter/IPAdapterMethod';
88
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
9+
import { useIsSavingCanvas, usePullBboxIntoIPAdapter } from 'features/controlLayers/hooks/saveCanvasHooks';
910
import {
1011
ipaBeginEndStepPctChanged,
1112
ipaCLIPVisionModelChanged,
@@ -18,12 +19,15 @@ import { selectCanvasSlice, selectEntityOrThrow } from 'features/controlLayers/s
1819
import type { CLIPVisionModelV2, IPMethodV2 } from 'features/controlLayers/store/types';
1920
import type { IPAImageDropData } from 'features/dnd/types';
2021
import { memo, useCallback, useMemo } from 'react';
22+
import { useTranslation } from 'react-i18next';
23+
import { PiBoundingBoxBold } from 'react-icons/pi';
2124
import type { ImageDTO, IPAdapterModelConfig, IPALayerImagePostUploadAction } from 'services/api/types';
2225

2326
import { IPAdapterImagePreview } from './IPAdapterImagePreview';
2427
import { IPAdapterModel } from './IPAdapterModel';
2528

2629
export const IPAdapterSettings = memo(() => {
30+
const { t } = useTranslation();
2731
const dispatch = useAppDispatch();
2832
const entityIdentifier = useEntityIdentifierContext('ip_adapter');
2933
const selectIPAdapter = useMemo(
@@ -82,6 +86,8 @@ export const IPAdapterSettings = memo(() => {
8286
() => ({ type: 'SET_IPA_IMAGE', id: entityIdentifier.id }),
8387
[entityIdentifier.id]
8488
);
89+
const pullBboxIntoIPAdapter = usePullBboxIntoIPAdapter(entityIdentifier);
90+
const isSaving = useIsSavingCanvas();
8591

8692
return (
8793
<CanvasEntitySettingsWrapper>
@@ -95,6 +101,14 @@ export const IPAdapterSettings = memo(() => {
95101
onChangeCLIPVisionModel={onChangeCLIPVisionModel}
96102
/>
97103
</Box>
104+
<IconButton
105+
onClick={pullBboxIntoIPAdapter}
106+
isLoading={isSaving.isTrue}
107+
variant="ghost"
108+
aria-label={t('controlLayers.pullBboxIntoIPAdapter')}
109+
tooltip={t('controlLayers.pullBboxIntoIPAdapter')}
110+
icon={<PiBoundingBoxBold />}
111+
/>
98112
</Flex>
99113
<Flex gap={4} w="full" alignItems="center">
100114
<Flex flexDir="column" gap={3} w="full">

invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RegionalGuidanceIPAdapterSettings.tsx

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ import { IPAdapterImagePreview } from 'features/controlLayers/components/IPAdapt
77
import { IPAdapterMethod } from 'features/controlLayers/components/IPAdapter/IPAdapterMethod';
88
import { IPAdapterModel } from 'features/controlLayers/components/IPAdapter/IPAdapterModel';
99
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
10+
import {
11+
useIsSavingCanvas,
12+
usePullBboxIntoRegionalGuidanceIPAdapter,
13+
} from 'features/controlLayers/hooks/saveCanvasHooks';
1014
import {
1115
rgIPAdapterBeginEndStepPctChanged,
1216
rgIPAdapterCLIPVisionModelChanged,
@@ -20,7 +24,8 @@ import { selectCanvasSlice, selectRegionalGuidanceIPAdapter } from 'features/con
2024
import type { CLIPVisionModelV2, IPMethodV2 } from 'features/controlLayers/store/types';
2125
import type { RGIPAdapterImageDropData } from 'features/dnd/types';
2226
import { memo, useCallback, useMemo } from 'react';
23-
import { PiTrashSimpleBold } from 'react-icons/pi';
27+
import { useTranslation } from 'react-i18next';
28+
import { PiBoundingBoxBold, PiTrashSimpleBold } from 'react-icons/pi';
2429
import type { ImageDTO, IPAdapterModelConfig, RGIPAdapterImagePostUploadAction } from 'services/api/types';
2530
import { assert } from 'tsafe';
2631

@@ -31,6 +36,7 @@ type Props = {
3136

3237
export const RegionalGuidanceIPAdapterSettings = memo(({ ipAdapterId, ipAdapterNumber }: Props) => {
3338
const entityIdentifier = useEntityIdentifierContext('regional_guidance');
39+
const { t } = useTranslation();
3440
const dispatch = useAppDispatch();
3541
const onDeleteIPAdapter = useCallback(() => {
3642
dispatch(rgIPAdapterDeleted({ entityIdentifier, ipAdapterId }));
@@ -100,6 +106,8 @@ export const RegionalGuidanceIPAdapterSettings = memo(({ ipAdapterId, ipAdapterN
100106
() => ({ type: 'SET_RG_IP_ADAPTER_IMAGE', id: entityIdentifier.id, ipAdapterId }),
101107
[entityIdentifier.id, ipAdapterId]
102108
);
109+
const pullBboxIntoIPAdapter = usePullBboxIntoRegionalGuidanceIPAdapter(entityIdentifier, ipAdapterId);
110+
const isSaving = useIsSavingCanvas();
103111

104112
return (
105113
<Flex flexDir="column" gap={3}>
@@ -125,6 +133,14 @@ export const RegionalGuidanceIPAdapterSettings = memo(({ ipAdapterId, ipAdapterN
125133
onChangeCLIPVisionModel={onChangeCLIPVisionModel}
126134
/>
127135
</Box>
136+
<IconButton
137+
onClick={pullBboxIntoIPAdapter}
138+
isLoading={isSaving.isTrue}
139+
variant="ghost"
140+
aria-label={t('controlLayers.pullBboxIntoIPAdapter')}
141+
tooltip={t('controlLayers.pullBboxIntoIPAdapter')}
142+
icon={<PiBoundingBoxBold />}
143+
/>
128144
</Flex>
129145
<Flex gap={4} w="full" alignItems="center">
130146
<Flex flexDir="column" gap={3} w="full">

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

Lines changed: 69 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,18 @@ import { isOk, withResultAsync } from 'common/util/result';
55
import { useCanvasManager } from 'features/controlLayers/contexts/CanvasManagerProviderGate';
66
import { selectDefaultControlAdapter, selectDefaultIPAdapter } from 'features/controlLayers/hooks/addLayerHooks';
77
import { getPrefixedId } from 'features/controlLayers/konva/util';
8-
import { controlLayerAdded, ipaAdded, rasterLayerAdded, rgAdded } from 'features/controlLayers/store/canvasSlice';
8+
import {
9+
controlLayerAdded,
10+
entityRasterized,
11+
ipaAdded,
12+
ipaImageChanged,
13+
rasterLayerAdded,
14+
rgAdded,
15+
rgIPAdapterImageChanged,
16+
} from 'features/controlLayers/store/canvasSlice';
917
import type {
1018
CanvasControlLayerState,
19+
CanvasEntityIdentifier,
1120
CanvasIPAdapterState,
1221
CanvasRasterLayerState,
1322
CanvasRegionalGuidanceState,
@@ -102,7 +111,7 @@ export const useSaveBboxAsRegionalGuidanceIPAdapter = () => {
102111
dispatch(rgAdded({ overrides, isSelected: true }));
103112
};
104113

105-
return { region: 'bbox', saveToGallery: true, onSave };
114+
return { region: 'bbox', saveToGallery: false, onSave };
106115
}, [defaultIPAdapter, dispatch]);
107116
const saveBboxAsRegionalGuidanceIPAdapter = useSaveCanvas(saveBboxAsRegionalGuidanceIPAdapterArg);
108117
return saveBboxAsRegionalGuidanceIPAdapter;
@@ -123,7 +132,7 @@ export const useSaveBboxAsGlobalIPAdapter = () => {
123132
dispatch(ipaAdded({ overrides, isSelected: true }));
124133
};
125134

126-
return { region: 'bbox', saveToGallery: true, onSave };
135+
return { region: 'bbox', saveToGallery: false, onSave };
127136
}, [defaultIPAdapter, dispatch]);
128137
const saveBboxAsIPAdapter = useSaveCanvas(saveBboxAsIPAdapterArg);
129138
return saveBboxAsIPAdapter;
@@ -140,7 +149,7 @@ export const useSaveBboxAsRasterLayer = () => {
140149
dispatch(rasterLayerAdded({ overrides, isSelected: true }));
141150
};
142151

143-
return { region: 'bbox', saveToGallery: true, onSave };
152+
return { region: 'bbox', saveToGallery: false, onSave };
144153
}, [dispatch]);
145154
const saveBboxAsRasterLayer = useSaveCanvas(saveBboxAsRasterLayerArg);
146155
return saveBboxAsRasterLayer;
@@ -160,8 +169,63 @@ export const useSaveBboxAsControlLayer = () => {
160169
dispatch(controlLayerAdded({ overrides, isSelected: true }));
161170
};
162171

163-
return { region: 'bbox', saveToGallery: true, onSave };
172+
return { region: 'bbox', saveToGallery: false, onSave };
164173
}, [defaultControlAdapter, dispatch]);
165174
const saveBboxAsControlLayer = useSaveCanvas(saveBboxAsControlLayerArg);
166175
return saveBboxAsControlLayer;
167176
};
177+
178+
export const usePullBboxIntoLayer = (entityIdentifier: CanvasEntityIdentifier<'control_layer' | 'raster_layer'>) => {
179+
const dispatch = useAppDispatch();
180+
181+
const pullBboxIntoLayerArg = useMemo<UseSaveCanvasArg>(() => {
182+
const onSave = (imageDTO: ImageDTO, rect: Rect) => {
183+
dispatch(
184+
entityRasterized({
185+
entityIdentifier,
186+
position: { x: rect.x, y: rect.y },
187+
imageObject: imageDTOToImageObject(imageDTO),
188+
replaceObjects: true,
189+
})
190+
);
191+
};
192+
193+
return { region: 'bbox', saveToGallery: false, onSave };
194+
}, [dispatch, entityIdentifier]);
195+
196+
const pullBboxIntoLayer = useSaveCanvas(pullBboxIntoLayerArg);
197+
return pullBboxIntoLayer;
198+
};
199+
200+
export const usePullBboxIntoIPAdapter = (entityIdentifier: CanvasEntityIdentifier<'ip_adapter'>) => {
201+
const dispatch = useAppDispatch();
202+
203+
const pullBboxIntoIPAdapterArg = useMemo<UseSaveCanvasArg>(() => {
204+
const onSave = (imageDTO: ImageDTO, _: Rect) => {
205+
dispatch(ipaImageChanged({ entityIdentifier, imageDTO }));
206+
};
207+
208+
return { region: 'bbox', saveToGallery: false, onSave };
209+
}, [dispatch, entityIdentifier]);
210+
211+
const pullBboxIntoIPAdapter = useSaveCanvas(pullBboxIntoIPAdapterArg);
212+
return pullBboxIntoIPAdapter;
213+
};
214+
215+
export const usePullBboxIntoRegionalGuidanceIPAdapter = (
216+
entityIdentifier: CanvasEntityIdentifier<'regional_guidance'>,
217+
ipAdapterId: string
218+
) => {
219+
const dispatch = useAppDispatch();
220+
221+
const pullBboxIntoRegionalGuidanceIPAdapterArg = useMemo<UseSaveCanvasArg>(() => {
222+
const onSave = (imageDTO: ImageDTO, _: Rect) => {
223+
dispatch(rgIPAdapterImageChanged({ entityIdentifier, ipAdapterId, imageDTO }));
224+
};
225+
226+
return { region: 'bbox', saveToGallery: false, onSave };
227+
}, [dispatch, entityIdentifier, ipAdapterId]);
228+
229+
const pullBboxIntoRegionalGuidanceIPAdapter = useSaveCanvas(pullBboxIntoRegionalGuidanceIPAdapterArg);
230+
return pullBboxIntoRegionalGuidanceIPAdapter;
231+
};

0 commit comments

Comments
 (0)