mirror of
https://github.com/invoke-ai/InvokeAI.git
synced 2026-04-23 03:00:31 -04:00
feat(ui): preserve mask
This commit is contained in:
@@ -1912,6 +1912,10 @@
|
||||
"label": "Snap to Grid",
|
||||
"on": "On",
|
||||
"off": "Off"
|
||||
},
|
||||
"preserveMask": {
|
||||
"label": "Preserve Mask Region",
|
||||
"alert": "Preserving Masked Region"
|
||||
}
|
||||
},
|
||||
"HUD": {
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import { Alert, AlertIcon, AlertTitle } from '@invoke-ai/ui-library';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { selectPreserveMask } from 'features/controlLayers/store/canvasSettingsSlice';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export const CanvasAlertsPreserveMask = memo(() => {
|
||||
const { t } = useTranslation();
|
||||
const preserveMask = useAppSelector(selectPreserveMask);
|
||||
|
||||
if (!preserveMask) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Alert status="warning" borderRadius="base" fontSize="sm" shadow="md">
|
||||
<AlertIcon />
|
||||
<AlertTitle>{t('controlLayers.settings.preserveMask.alert')}</AlertTitle>
|
||||
</Alert>
|
||||
);
|
||||
});
|
||||
|
||||
CanvasAlertsPreserveMask.displayName = 'CanvasAlertsPreserveMask';
|
||||
@@ -30,7 +30,7 @@ type AlertData = {
|
||||
description: string;
|
||||
};
|
||||
|
||||
const CanvasSelectedEntityStatusAlertContent = memo(({ entityIdentifier, adapter }: ContentProps) => {
|
||||
const CanvasAlertsSelectedEntityStatusContent = memo(({ entityIdentifier, adapter }: ContentProps) => {
|
||||
const { t } = useTranslation();
|
||||
const title = useEntityTitle(entityIdentifier);
|
||||
const selectIsEnabled = useMemo(
|
||||
@@ -113,9 +113,9 @@ const CanvasSelectedEntityStatusAlertContent = memo(({ entityIdentifier, adapter
|
||||
);
|
||||
});
|
||||
|
||||
CanvasSelectedEntityStatusAlertContent.displayName = 'CanvasSelectedEntityStatusAlertContent';
|
||||
CanvasAlertsSelectedEntityStatusContent.displayName = 'CanvasAlertsSelectedEntityStatusContent';
|
||||
|
||||
export const CanvasSelectedEntityStatusAlert = memo(() => {
|
||||
export const CanvasAlertsSelectedEntityStatus = memo(() => {
|
||||
const selectedEntityIdentifier = useAppSelector(selectSelectedEntityIdentifier);
|
||||
const adapter = useEntityAdapterSafe(selectedEntityIdentifier);
|
||||
|
||||
@@ -123,7 +123,7 @@ export const CanvasSelectedEntityStatusAlert = memo(() => {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <CanvasSelectedEntityStatusAlertContent entityIdentifier={selectedEntityIdentifier} adapter={adapter} />;
|
||||
return <CanvasAlertsSelectedEntityStatusContent entityIdentifier={selectedEntityIdentifier} adapter={adapter} />;
|
||||
});
|
||||
|
||||
CanvasSelectedEntityStatusAlert.displayName = 'CanvasSelectedEntityStatusAlert';
|
||||
CanvasAlertsSelectedEntityStatus.displayName = 'CanvasAlertsSelectedEntityStatus';
|
||||
@@ -38,7 +38,7 @@ const ActivateImageViewerButton = (props: PropsWithChildren) => {
|
||||
);
|
||||
};
|
||||
|
||||
export const SendingToGalleryAlert = () => {
|
||||
export const CanvasAlertsSendingToGallery = () => {
|
||||
const { t } = useTranslation();
|
||||
const destination = useCurrentDestination();
|
||||
const isVisible = useMemo(() => {
|
||||
@@ -79,7 +79,7 @@ const ActivateCanvasButton = (props: PropsWithChildren) => {
|
||||
);
|
||||
};
|
||||
|
||||
export const SendingToCanvasAlert = () => {
|
||||
export const CanvasAlertsSendingToCanvas = () => {
|
||||
const { t } = useTranslation();
|
||||
const destination = useCurrentDestination();
|
||||
const isVisible = useMemo(() => {
|
||||
@@ -1,13 +1,14 @@
|
||||
import { ContextMenu, Flex, MenuList } from '@invoke-ai/ui-library';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { useScopeOnFocus } from 'common/hooks/interactionScopes';
|
||||
import { CanvasAlertsPreserveMask } from 'features/controlLayers/components/CanvasAlerts/CanvasAlertsPreserveMask';
|
||||
import { CanvasAlertsSelectedEntityStatus } from 'features/controlLayers/components/CanvasAlerts/CanvasAlertsSelectedEntityStatus';
|
||||
import { CanvasAlertsSendingToGallery } from 'features/controlLayers/components/CanvasAlerts/CanvasAlertsSendingTo';
|
||||
import { CanvasContextMenuGlobalMenuItems } from 'features/controlLayers/components/CanvasContextMenu/CanvasContextMenuGlobalMenuItems';
|
||||
import { CanvasContextMenuSelectedEntityMenuItems } from 'features/controlLayers/components/CanvasContextMenu/CanvasContextMenuSelectedEntityMenuItems';
|
||||
import { CanvasDropArea } from 'features/controlLayers/components/CanvasDropArea';
|
||||
import { Filter } from 'features/controlLayers/components/Filters/Filter';
|
||||
import { CanvasHUD } from 'features/controlLayers/components/HUD/CanvasHUD';
|
||||
import { CanvasSelectedEntityStatusAlert } from 'features/controlLayers/components/HUD/CanvasSelectedEntityStatusAlert';
|
||||
import { SendingToGalleryAlert } from 'features/controlLayers/components/HUD/CanvasSendingToGalleryAlert';
|
||||
import { InvokeCanvasComponent } from 'features/controlLayers/components/InvokeCanvasComponent';
|
||||
import { StagingAreaIsStagingGate } from 'features/controlLayers/components/StagingArea/StagingAreaIsStagingGate';
|
||||
import { StagingAreaToolbar } from 'features/controlLayers/components/StagingArea/StagingAreaToolbar';
|
||||
@@ -82,8 +83,9 @@ export const CanvasMainPanelContent = memo(() => {
|
||||
</Flex>
|
||||
)}
|
||||
<Flex flexDir="column" position="absolute" top={1} insetInlineEnd={1} pointerEvents="none" gap={2}>
|
||||
<CanvasSelectedEntityStatusAlert />
|
||||
<SendingToGalleryAlert />
|
||||
<CanvasAlertsSelectedEntityStatus />
|
||||
<CanvasAlertsPreserveMask />
|
||||
<CanvasAlertsSendingToGallery />
|
||||
</Flex>
|
||||
</CanvasManagerProviderGate>
|
||||
</Flex>
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
import { FormControl, FormLabel, Switch } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import {
|
||||
selectBboxOverlay,
|
||||
settingsBboxOverlayToggled,
|
||||
} from 'features/controlLayers/store/canvasSettingsSlice';
|
||||
import { selectBboxOverlay, settingsBboxOverlayToggled } from 'features/controlLayers/store/canvasSettingsSlice';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ import { CanvasSettingsDynamicGridSwitch } from 'features/controlLayers/componen
|
||||
import { CanvasSettingsSnapToGridCheckbox } from 'features/controlLayers/components/Settings/CanvasSettingsGridSize';
|
||||
import { CanvasSettingsInvertScrollCheckbox } from 'features/controlLayers/components/Settings/CanvasSettingsInvertScrollCheckbox';
|
||||
import { CanvasSettingsLogDebugInfoButton } from 'features/controlLayers/components/Settings/CanvasSettingsLogDebugInfo';
|
||||
import { CanvasSettingsPreserveMaskCheckbox } from 'features/controlLayers/components/Settings/CanvasSettingsPreserveMaskCheckbox';
|
||||
import { CanvasSettingsRecalculateRectsButton } from 'features/controlLayers/components/Settings/CanvasSettingsRecalculateRectsButton';
|
||||
import { CanvasSettingsShowHUDSwitch } from 'features/controlLayers/components/Settings/CanvasSettingsShowHUDSwitch';
|
||||
import { CanvasSettingsShowProgressOnCanvas } from 'features/controlLayers/components/Settings/CanvasSettingsShowProgressOnCanvasSwitch';
|
||||
@@ -39,6 +40,7 @@ export const CanvasSettingsPopover = memo(() => {
|
||||
<Flex direction="column" gap={2}>
|
||||
<CanvasSettingsAutoSaveCheckbox />
|
||||
<CanvasSettingsInvertScrollCheckbox />
|
||||
<CanvasSettingsPreserveMaskCheckbox />
|
||||
<CanvasSettingsClipToBboxCheckbox />
|
||||
<CanvasSettingsCompositeMaskedRegionsCheckbox />
|
||||
<CanvasSettingsSnapToGridCheckbox />
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import { Checkbox, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { selectPreserveMask, settingsPreserveMaskToggled } from 'features/controlLayers/store/canvasSettingsSlice';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export const CanvasSettingsPreserveMaskCheckbox = memo(() => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
const preserveMask = useAppSelector(selectPreserveMask);
|
||||
const onChange = useCallback(() => dispatch(settingsPreserveMaskToggled()), [dispatch]);
|
||||
return (
|
||||
<FormControl w="full">
|
||||
<FormLabel flexGrow={1}>{t('controlLayers.settings.preserveMask.label')}</FormLabel>
|
||||
<Checkbox isChecked={preserveMask} onChange={onChange} />
|
||||
</FormControl>
|
||||
);
|
||||
});
|
||||
|
||||
CanvasSettingsPreserveMaskCheckbox.displayName = 'CanvasSettingsPreserveMaskCheckbox';
|
||||
@@ -68,6 +68,10 @@ type CanvasSettingsState = {
|
||||
* Whether to show the bounding box overlay on the canvas.
|
||||
*/
|
||||
bboxOverlay: boolean;
|
||||
/**
|
||||
* Whether to preserve the masked region instead of inpainting it.
|
||||
*/
|
||||
preserveMask: boolean;
|
||||
// TODO(psyche): These are copied from old canvas state, need to be implemented
|
||||
// imageSmoothing: boolean;
|
||||
// preserveMaskedArea: boolean;
|
||||
@@ -89,6 +93,7 @@ const initialState: CanvasSettingsState = {
|
||||
snapToGrid: true,
|
||||
showProgressOnCanvas: true,
|
||||
bboxOverlay: false,
|
||||
preserveMask: false,
|
||||
};
|
||||
|
||||
export const canvasSettingsSlice = createSlice({
|
||||
@@ -137,6 +142,9 @@ export const canvasSettingsSlice = createSlice({
|
||||
settingsBboxOverlayToggled: (state) => {
|
||||
state.bboxOverlay = !state.bboxOverlay;
|
||||
},
|
||||
settingsPreserveMaskToggled: (state) => {
|
||||
state.preserveMask = !state.preserveMask;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -155,6 +163,7 @@ export const {
|
||||
settingsSnapToGridToggled,
|
||||
settingsShowProgressOnCanvasToggled,
|
||||
settingsBboxOverlayToggled,
|
||||
settingsPreserveMaskToggled,
|
||||
} = canvasSettingsSlice.actions;
|
||||
|
||||
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
|
||||
@@ -174,6 +183,7 @@ const createCanvasSettingsSelector = <T>(selector: Selector<CanvasSettingsState,
|
||||
createSelector(selectCanvasSettingsSlice, selector);
|
||||
|
||||
export const selectAutoSave = createCanvasSettingsSelector((settings) => settings.autoSave);
|
||||
export const selectPreserveMask = createCanvasSettingsSelector((settings) => settings.preserveMask);
|
||||
export const selectDynamicGrid = createCanvasSettingsSelector((settings) => settings.dynamicGrid);
|
||||
export const selectBboxOverlay = createCanvasSettingsSelector((settings) => settings.bboxOverlay);
|
||||
export const selectShowHUD = createCanvasSettingsSelector((settings) => settings.showHUD);
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Box, Button, Flex } from '@invoke-ai/ui-library';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { useScopeOnFocus, useScopeOnMount } from 'common/hooks/interactionScopes';
|
||||
import { useAssertSingleton } from 'common/hooks/useAssertSingleton';
|
||||
import { SendingToCanvasAlert } from 'features/controlLayers/components/HUD/CanvasSendingToGalleryAlert';
|
||||
import { CanvasAlertsSendingToCanvas } from 'features/controlLayers/components/CanvasAlerts/CanvasAlertsSendingTo';
|
||||
import { CompareToolbar } from 'features/gallery/components/ImageViewer/CompareToolbar';
|
||||
import CurrentImagePreview from 'features/gallery/components/ImageViewer/CurrentImagePreview';
|
||||
import { ImageComparison } from 'features/gallery/components/ImageViewer/ImageComparison';
|
||||
@@ -58,7 +58,7 @@ export const ImageViewer = memo(({ closeButton }: Props) => {
|
||||
</Box>
|
||||
<ImageComparisonDroppable />
|
||||
<Box position="absolute" top={14} insetInlineEnd={2}>
|
||||
<SendingToCanvasAlert />
|
||||
<CanvasAlertsSendingToCanvas />
|
||||
</Box>
|
||||
</Flex>
|
||||
);
|
||||
|
||||
@@ -46,7 +46,7 @@ export const addInpaint = async (
|
||||
id: getPrefixedId('alpha_to_mask'),
|
||||
type: 'tomask',
|
||||
image: { image_name: maskImage.image_name },
|
||||
invert: true,
|
||||
invert: !canvasSettings.preserveMask,
|
||||
});
|
||||
const resizeMaskToScaledSize = g.addNode({
|
||||
id: getPrefixedId('resize_mask_to_scaled_size'),
|
||||
@@ -115,7 +115,7 @@ export const addInpaint = async (
|
||||
id: getPrefixedId('alpha_to_mask'),
|
||||
type: 'tomask',
|
||||
image: { image_name: maskImage.image_name },
|
||||
invert: true,
|
||||
invert: !canvasSettings.preserveMask,
|
||||
});
|
||||
const createGradientMask = g.addNode({
|
||||
id: getPrefixedId('create_gradient_mask'),
|
||||
|
||||
@@ -43,7 +43,7 @@ export const addOutpaint = async (
|
||||
id: getPrefixedId('alpha_to_mask'),
|
||||
type: 'tomask',
|
||||
image: { image_name: maskImage.image_name },
|
||||
invert: true,
|
||||
invert: !canvasSettings.preserveMask,
|
||||
});
|
||||
const initialImageAlphaToMask = g.addNode({
|
||||
id: getPrefixedId('image_alpha_to_mask'),
|
||||
@@ -135,7 +135,7 @@ export const addOutpaint = async (
|
||||
id: getPrefixedId('mask_alpha_to_mask'),
|
||||
type: 'tomask',
|
||||
image: { image_name: maskImage.image_name },
|
||||
invert: true,
|
||||
invert: !canvasSettings.preserveMask,
|
||||
});
|
||||
const initialImageAlphaToMask = g.addNode({
|
||||
id: getPrefixedId('image_alpha_to_mask'),
|
||||
|
||||
Reference in New Issue
Block a user