diff --git a/invokeai/frontend/web/src/features/controlLayers/components/Filters/Filter.tsx b/invokeai/frontend/web/src/features/controlLayers/components/Filters/Filter.tsx index 93ac679af83..4143b113ca3 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/Filters/Filter.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/Filters/Filter.tsx @@ -1,4 +1,14 @@ -import { Button, ButtonGroup, Flex, Heading, Spacer } from '@invoke-ai/ui-library'; +import { + Button, + ButtonGroup, + Flex, + Heading, + Menu, + MenuButton, + MenuItem, + MenuList, + Spacer, +} from '@invoke-ai/ui-library'; import { useStore } from '@nanostores/react'; import { useAppSelector } from 'app/store/storeHooks'; import { useFocusRegion, useIsRegionFocused } from 'common/hooks/focus'; @@ -15,7 +25,7 @@ import { IMAGE_FILTERS } from 'features/controlLayers/store/filters'; import { useRegisteredHotkeys } from 'features/system/components/HotkeysModal/useHotkeyData'; import { memo, useCallback, useMemo, useRef } from 'react'; import { useTranslation } from 'react-i18next'; -import { PiArrowsCounterClockwiseBold, PiCheckBold, PiPlayFill, PiXBold } from 'react-icons/pi'; +import { PiArrowsCounterClockwiseBold, PiFloppyDiskBold, PiPlayFill, PiXBold } from 'react-icons/pi'; const FilterContent = memo( ({ adapter }: { adapter: CanvasEntityAdapterRasterLayer | CanvasEntityAdapterControlLayer }) => { @@ -46,6 +56,22 @@ const FilterContent = memo( return IMAGE_FILTERS[config.type].validateConfig?.(config as never) ?? true; }, [config]); + const saveAsInpaintMask = useCallback(() => { + adapter.filterer.saveAs('inpaint_mask'); + }, [adapter.filterer]); + + const saveAsRegionalGuidance = useCallback(() => { + adapter.filterer.saveAs('regional_guidance'); + }, [adapter.filterer]); + + const saveAsRasterLayer = useCallback(() => { + adapter.filterer.saveAs('raster_layer'); + }, [adapter.filterer]); + + const saveAsControlLayer = useCallback(() => { + adapter.filterer.saveAs('control_layer'); + }, [adapter.filterer]); + useRegisteredHotkeys({ id: 'applyFilter', category: 'canvas', @@ -107,16 +133,35 @@ const FilterContent = memo( > {t('controlLayers.filter.reset')} - } - onClick={adapter.filterer.apply} - isLoading={isProcessing} - loadingText={t('controlLayers.filter.apply')} - isDisabled={!isValid || !hasProcessed} - > - {t('controlLayers.filter.apply')} - +
} diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasEntity/CanvasEntityFilterer.ts b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasEntity/CanvasEntityFilterer.ts index d2e9a82338e..597c9e1c2e7 100644 --- a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasEntity/CanvasEntityFilterer.ts +++ b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasEntity/CanvasEntityFilterer.ts @@ -7,7 +7,7 @@ import { getPrefixedId } from 'features/controlLayers/konva/util'; import { selectAutoProcess } from 'features/controlLayers/store/canvasSettingsSlice'; import type { FilterConfig } from 'features/controlLayers/store/filters'; import { getFilterForModel, IMAGE_FILTERS } from 'features/controlLayers/store/filters'; -import type { CanvasImageState } from 'features/controlLayers/store/types'; +import type { CanvasEntityType, CanvasImageState } from 'features/controlLayers/store/types'; import { imageDTOToImageObject } from 'features/controlLayers/store/util'; import { debounce } from 'lodash-es'; import { atom } from 'nanostores'; @@ -15,6 +15,8 @@ import type { Logger } from 'roarr'; import { serializeError } from 'serialize-error'; import { buildSelectModelConfig } from 'services/api/hooks/modelsByType'; import { isControlNetOrT2IAdapterModelConfig } from 'services/api/types'; +import type { Equals } from 'tsafe'; +import { assert } from 'tsafe'; type CanvasEntityFiltererConfig = { processDebounceMs: number; @@ -220,6 +222,50 @@ export class CanvasEntityFilterer extends CanvasModuleBase { this.manager.stateApi.$filteringAdapter.set(null); }; + saveAs = (type: Exclude