Skip to content

Commit 7640ee3

Browse files
hipsterusernamepsychedelicious
authored andcommitted
feat(ui):Adjust-bbox-to-masks
1 parent 1f5f70f commit 7640ee3

File tree

5 files changed

+93
-0
lines changed

5 files changed

+93
-0
lines changed

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -599,6 +599,10 @@
599599
"toggleNonRasterLayers": {
600600
"title": "Toggle Non-Raster Layers",
601601
"desc": "Show or hide all non-raster layer categories (Control Layers, Inpaint Masks, Regional Guidance)."
602+
},
603+
"fitBboxToMasks": {
604+
"title": "Fit Bbox To Masks",
605+
"desc": "Automatically adjust the generation bounding box to fit visible inpaint masks"
602606
}
603607
},
604608
"workflows": {
@@ -1940,6 +1944,7 @@
19401944
"canvas": "Canvas",
19411945
"bookmark": "Bookmark for Quick Switch",
19421946
"fitBboxToLayers": "Fit Bbox To Layers",
1947+
"fitBboxToMasks": "Fit Bbox To Masks",
19431948
"removeBookmark": "Remove Bookmark",
19441949
"saveCanvasToGallery": "Save Canvas to Gallery",
19451950
"saveBboxToGallery": "Save Bbox to Gallery",

invokeai/frontend/web/src/features/controlLayers/components/Toolbar/CanvasToolbar.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { CanvasSettingsPopover } from 'features/controlLayers/components/Setting
33
import { ToolColorPicker } from 'features/controlLayers/components/Tool/ToolFillColorPicker';
44
import { ToolSettings } from 'features/controlLayers/components/Tool/ToolSettings';
55
import { CanvasToolbarFitBboxToLayersButton } from 'features/controlLayers/components/Toolbar/CanvasToolbarFitBboxToLayersButton';
6+
import { CanvasToolbarFitBboxToMasksButton } from 'features/controlLayers/components/Toolbar/CanvasToolbarFitBboxToMasksButton';
67
import { CanvasToolbarNewSessionMenuButton } from 'features/controlLayers/components/Toolbar/CanvasToolbarNewSessionMenuButton';
78
import { CanvasToolbarRedoButton } from 'features/controlLayers/components/Toolbar/CanvasToolbarRedoButton';
89
import { CanvasToolbarResetViewButton } from 'features/controlLayers/components/Toolbar/CanvasToolbarResetViewButton';
@@ -37,6 +38,7 @@ export const CanvasToolbar = memo(() => {
3738
<CanvasToolbarScale />
3839
<CanvasToolbarResetViewButton />
3940
<CanvasToolbarFitBboxToLayersButton />
41+
<CanvasToolbarFitBboxToMasksButton />
4042
</Flex>
4143
<Divider orientation="vertical" />
4244
<Flex alignItems="center" h="full">
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { IconButton } from '@invoke-ai/ui-library';
2+
import { useAutoFitBBoxToMasks } from 'features/controlLayers/hooks/useAutoFitBBoxToMasks';
3+
import { useCanvasIsBusy } from 'features/controlLayers/hooks/useCanvasIsBusy';
4+
import { useVisibleEntityCountByType } from 'features/controlLayers/hooks/useVisibleEntityCountByType';
5+
import { useRegisteredHotkeys } from 'features/system/components/HotkeysModal/useHotkeyData';
6+
import { memo, useCallback } from 'react';
7+
import { useTranslation } from 'react-i18next';
8+
import { PiSelectionAllDuotone } from 'react-icons/pi';
9+
10+
export const CanvasToolbarFitBboxToMasksButton = memo(() => {
11+
const { t } = useTranslation();
12+
const isBusy = useCanvasIsBusy();
13+
const fitBBoxToMasks = useAutoFitBBoxToMasks();
14+
15+
// Check if there are any visible inpaint masks
16+
const visibleMaskCount = useVisibleEntityCountByType('inpaint_mask');
17+
const hasVisibleMasks = visibleMaskCount > 0;
18+
19+
const onClick = useCallback(() => {
20+
fitBBoxToMasks();
21+
}, [fitBBoxToMasks]);
22+
23+
// Register hotkey for Shift+B
24+
useRegisteredHotkeys({
25+
id: 'fitBboxToMasks',
26+
category: 'canvas',
27+
callback: fitBBoxToMasks,
28+
options: { enabled: !isBusy && hasVisibleMasks },
29+
dependencies: [fitBBoxToMasks, isBusy, hasVisibleMasks],
30+
});
31+
32+
return (
33+
<IconButton
34+
onClick={onClick}
35+
variant="link"
36+
alignSelf="stretch"
37+
aria-label={t('controlLayers.fitBboxToMasks')}
38+
tooltip={t('controlLayers.fitBboxToMasks')}
39+
icon={<PiSelectionAllDuotone />}
40+
isDisabled={isBusy || !hasVisibleMasks}
41+
/>
42+
);
43+
});
44+
45+
CanvasToolbarFitBboxToMasksButton.displayName = 'CanvasToolbarFitBboxToMasksButton';
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { useAppSelector } from 'app/store/storeHooks';
2+
import { useCanvasManager } from 'features/controlLayers/contexts/CanvasManagerProviderGate';
3+
import { fitRectToGrid } from 'features/controlLayers/konva/util';
4+
import { selectMaskBlur } from 'features/controlLayers/store/paramsSlice';
5+
import { useCallback } from 'react';
6+
7+
export const useAutoFitBBoxToMasks = () => {
8+
const canvasManager = useCanvasManager();
9+
const maskBlur = useAppSelector(selectMaskBlur);
10+
11+
const fitBBoxToMasks = useCallback(() => {
12+
// Get the rect of all visible inpaint masks
13+
const visibleRect = canvasManager.compositor.getVisibleRectOfType('inpaint_mask');
14+
15+
// Can't fit the bbox to nothing
16+
if (visibleRect.height === 0 || visibleRect.width === 0) {
17+
return;
18+
}
19+
20+
// Account for mask blur expansion and add 8px padding
21+
const padding = 8;
22+
const totalPadding = maskBlur + padding;
23+
24+
const expandedRect = {
25+
x: visibleRect.x - totalPadding,
26+
y: visibleRect.y - totalPadding,
27+
width: visibleRect.width + totalPadding * 2,
28+
height: visibleRect.height + totalPadding * 2,
29+
};
30+
31+
// Apply grid fitting using the bbox grid size
32+
const gridSize = canvasManager.stateApi.getBboxGridSize();
33+
const rect = fitRectToGrid(expandedRect, gridSize);
34+
35+
// Update the generation bbox
36+
canvasManager.stateApi.setGenerationBbox(rect);
37+
}, [canvasManager, maskBlur]);
38+
39+
return fitBBoxToMasks;
40+
};

invokeai/frontend/web/src/features/system/components/HotkeysModal/useHotkeyData.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ export const useHotkeyData = (): HotkeysData => {
123123
addHotkey('canvas', 'applySegmentAnything', ['enter']);
124124
addHotkey('canvas', 'cancelSegmentAnything', ['esc']);
125125
addHotkey('canvas', 'toggleNonRasterLayers', ['shift+h']);
126+
addHotkey('canvas', 'fitBboxToMasks', ['shift+b']);
126127

127128
// Workflows
128129
addHotkey('workflows', 'addNode', ['shift+a', 'space']);

0 commit comments

Comments
 (0)