feat(ui): preserve mask

This commit is contained in:
psychedelicious
2024-09-14 12:53:27 +10:00
parent ae6bf6f5c0
commit f9232cd45f
12 changed files with 79 additions and 21 deletions

View File

@@ -1912,6 +1912,10 @@
"label": "Snap to Grid",
"on": "On",
"off": "Off"
},
"preserveMask": {
"label": "Preserve Mask Region",
"alert": "Preserving Masked Region"
}
},
"HUD": {

View File

@@ -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';

View File

@@ -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';

View File

@@ -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(() => {

View File

@@ -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>

View File

@@ -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';

View File

@@ -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 />

View File

@@ -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';

View File

@@ -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);

View File

@@ -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>
);

View File

@@ -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'),

View File

@@ -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'),