feat(ui):Adjust-bbox-to-masks

This commit is contained in:
Kent Keirsey
2025-07-20 09:52:07 -04:00
committed by psychedelicious
parent 1f5f70f898
commit 7640ee307c
5 changed files with 93 additions and 0 deletions

View File

@@ -599,6 +599,10 @@
"toggleNonRasterLayers": {
"title": "Toggle Non-Raster Layers",
"desc": "Show or hide all non-raster layer categories (Control Layers, Inpaint Masks, Regional Guidance)."
},
"fitBboxToMasks": {
"title": "Fit Bbox To Masks",
"desc": "Automatically adjust the generation bounding box to fit visible inpaint masks"
}
},
"workflows": {
@@ -1940,6 +1944,7 @@
"canvas": "Canvas",
"bookmark": "Bookmark for Quick Switch",
"fitBboxToLayers": "Fit Bbox To Layers",
"fitBboxToMasks": "Fit Bbox To Masks",
"removeBookmark": "Remove Bookmark",
"saveCanvasToGallery": "Save Canvas to Gallery",
"saveBboxToGallery": "Save Bbox to Gallery",

View File

@@ -3,6 +3,7 @@ import { CanvasSettingsPopover } from 'features/controlLayers/components/Setting
import { ToolColorPicker } from 'features/controlLayers/components/Tool/ToolFillColorPicker';
import { ToolSettings } from 'features/controlLayers/components/Tool/ToolSettings';
import { CanvasToolbarFitBboxToLayersButton } from 'features/controlLayers/components/Toolbar/CanvasToolbarFitBboxToLayersButton';
import { CanvasToolbarFitBboxToMasksButton } from 'features/controlLayers/components/Toolbar/CanvasToolbarFitBboxToMasksButton';
import { CanvasToolbarNewSessionMenuButton } from 'features/controlLayers/components/Toolbar/CanvasToolbarNewSessionMenuButton';
import { CanvasToolbarRedoButton } from 'features/controlLayers/components/Toolbar/CanvasToolbarRedoButton';
import { CanvasToolbarResetViewButton } from 'features/controlLayers/components/Toolbar/CanvasToolbarResetViewButton';
@@ -37,6 +38,7 @@ export const CanvasToolbar = memo(() => {
<CanvasToolbarScale />
<CanvasToolbarResetViewButton />
<CanvasToolbarFitBboxToLayersButton />
<CanvasToolbarFitBboxToMasksButton />
</Flex>
<Divider orientation="vertical" />
<Flex alignItems="center" h="full">

View File

@@ -0,0 +1,45 @@
import { IconButton } from '@invoke-ai/ui-library';
import { useAutoFitBBoxToMasks } from 'features/controlLayers/hooks/useAutoFitBBoxToMasks';
import { useCanvasIsBusy } from 'features/controlLayers/hooks/useCanvasIsBusy';
import { useVisibleEntityCountByType } from 'features/controlLayers/hooks/useVisibleEntityCountByType';
import { useRegisteredHotkeys } from 'features/system/components/HotkeysModal/useHotkeyData';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { PiSelectionAllDuotone } from 'react-icons/pi';
export const CanvasToolbarFitBboxToMasksButton = memo(() => {
const { t } = useTranslation();
const isBusy = useCanvasIsBusy();
const fitBBoxToMasks = useAutoFitBBoxToMasks();
// Check if there are any visible inpaint masks
const visibleMaskCount = useVisibleEntityCountByType('inpaint_mask');
const hasVisibleMasks = visibleMaskCount > 0;
const onClick = useCallback(() => {
fitBBoxToMasks();
}, [fitBBoxToMasks]);
// Register hotkey for Shift+B
useRegisteredHotkeys({
id: 'fitBboxToMasks',
category: 'canvas',
callback: fitBBoxToMasks,
options: { enabled: !isBusy && hasVisibleMasks },
dependencies: [fitBBoxToMasks, isBusy, hasVisibleMasks],
});
return (
<IconButton
onClick={onClick}
variant="link"
alignSelf="stretch"
aria-label={t('controlLayers.fitBboxToMasks')}
tooltip={t('controlLayers.fitBboxToMasks')}
icon={<PiSelectionAllDuotone />}
isDisabled={isBusy || !hasVisibleMasks}
/>
);
});
CanvasToolbarFitBboxToMasksButton.displayName = 'CanvasToolbarFitBboxToMasksButton';

View File

@@ -0,0 +1,40 @@
import { useAppSelector } from 'app/store/storeHooks';
import { useCanvasManager } from 'features/controlLayers/contexts/CanvasManagerProviderGate';
import { fitRectToGrid } from 'features/controlLayers/konva/util';
import { selectMaskBlur } from 'features/controlLayers/store/paramsSlice';
import { useCallback } from 'react';
export const useAutoFitBBoxToMasks = () => {
const canvasManager = useCanvasManager();
const maskBlur = useAppSelector(selectMaskBlur);
const fitBBoxToMasks = useCallback(() => {
// Get the rect of all visible inpaint masks
const visibleRect = canvasManager.compositor.getVisibleRectOfType('inpaint_mask');
// Can't fit the bbox to nothing
if (visibleRect.height === 0 || visibleRect.width === 0) {
return;
}
// Account for mask blur expansion and add 8px padding
const padding = 8;
const totalPadding = maskBlur + padding;
const expandedRect = {
x: visibleRect.x - totalPadding,
y: visibleRect.y - totalPadding,
width: visibleRect.width + totalPadding * 2,
height: visibleRect.height + totalPadding * 2,
};
// Apply grid fitting using the bbox grid size
const gridSize = canvasManager.stateApi.getBboxGridSize();
const rect = fitRectToGrid(expandedRect, gridSize);
// Update the generation bbox
canvasManager.stateApi.setGenerationBbox(rect);
}, [canvasManager, maskBlur]);
return fitBBoxToMasks;
};

View File

@@ -123,6 +123,7 @@ export const useHotkeyData = (): HotkeysData => {
addHotkey('canvas', 'applySegmentAnything', ['enter']);
addHotkey('canvas', 'cancelSegmentAnything', ['esc']);
addHotkey('canvas', 'toggleNonRasterLayers', ['shift+h']);
addHotkey('canvas', 'fitBboxToMasks', ['shift+b']);
// Workflows
addHotkey('workflows', 'addNode', ['shift+a', 'space']);