From 88dcb388dcaa8c2f135a6d95880e03b5d80350ac Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Wed, 11 Sep 2024 21:47:33 +1000 Subject: [PATCH] feat(ui): pull bbox into functionality for control/ip adapters --- invokeai/frontend/web/public/locales/en.json | 2 + .../ControlLayerControlAdapter.tsx | 14 +++- .../IPAdapter/IPAdapterSettings.tsx | 16 +++- .../RegionalGuidanceIPAdapterSettings.tsx | 18 ++++- .../controlLayers/hooks/saveCanvasHooks.ts | 74 +++++++++++++++++-- 5 files changed, 116 insertions(+), 8 deletions(-) diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json index 4cd7c018c95..d6183458c46 100644 --- a/invokeai/frontend/web/public/locales/en.json +++ b/invokeai/frontend/web/public/locales/en.json @@ -1782,6 +1782,8 @@ "flipVertical": "Flip Vertical", "stagingOnCanvas": "Staging images on", "replaceLayer": "Replace Layer", + "pullBboxIntoLayer": "Pull Bbox into Layer", + "pullBboxIntoIPAdapter": "Pull Bbox into IP Adapter", "fill": { "fillColor": "Fill Color", "fillStyle": "Fill Style", diff --git a/invokeai/frontend/web/src/features/controlLayers/components/ControlLayer/ControlLayerControlAdapter.tsx b/invokeai/frontend/web/src/features/controlLayers/components/ControlLayer/ControlLayerControlAdapter.tsx index 8493465c500..cd119b83e0d 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/ControlLayer/ControlLayerControlAdapter.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/ControlLayer/ControlLayerControlAdapter.tsx @@ -6,6 +6,7 @@ import { Weight } from 'features/controlLayers/components/common/Weight'; import { ControlLayerControlAdapterControlMode } from 'features/controlLayers/components/ControlLayer/ControlLayerControlAdapterControlMode'; import { ControlLayerControlAdapterModel } from 'features/controlLayers/components/ControlLayer/ControlLayerControlAdapterModel'; import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext'; +import { useIsSavingCanvas, usePullBboxIntoLayer } from 'features/controlLayers/hooks/saveCanvasHooks'; import { useEntityFilter } from 'features/controlLayers/hooks/useEntityFilter'; import { controlLayerBeginEndStepPctChanged, @@ -17,7 +18,7 @@ import { selectCanvasSlice, selectEntityOrThrow } from 'features/controlLayers/s import type { CanvasEntityIdentifier, ControlModeV2 } from 'features/controlLayers/store/types'; import { memo, useCallback, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; -import { PiShootingStarBold } from 'react-icons/pi'; +import { PiBoundingBoxBold, PiShootingStarBold } from 'react-icons/pi'; import type { ControlNetModelConfig, T2IAdapterModelConfig } from 'services/api/types'; const useControlLayerControlAdapter = (entityIdentifier: CanvasEntityIdentifier<'control_layer'>) => { @@ -68,6 +69,9 @@ export const ControlLayerControlAdapter = memo(() => { [dispatch, entityIdentifier] ); + const pullBboxIntoLayer = usePullBboxIntoLayer(entityIdentifier); + const isSaving = useIsSavingCanvas(); + return ( @@ -80,6 +84,14 @@ export const ControlLayerControlAdapter = memo(() => { tooltip={t('controlLayers.filter.filter')} icon={} /> + } + /> diff --git a/invokeai/frontend/web/src/features/controlLayers/components/IPAdapter/IPAdapterSettings.tsx b/invokeai/frontend/web/src/features/controlLayers/components/IPAdapter/IPAdapterSettings.tsx index 44b708a2389..4bd82069d2c 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/IPAdapter/IPAdapterSettings.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/IPAdapter/IPAdapterSettings.tsx @@ -1,4 +1,4 @@ -import { Box, Flex } from '@invoke-ai/ui-library'; +import { Box, Flex, IconButton } from '@invoke-ai/ui-library'; import { createSelector } from '@reduxjs/toolkit'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { BeginEndStepPct } from 'features/controlLayers/components/common/BeginEndStepPct'; @@ -6,6 +6,7 @@ import { CanvasEntitySettingsWrapper } from 'features/controlLayers/components/c import { Weight } from 'features/controlLayers/components/common/Weight'; import { IPAdapterMethod } from 'features/controlLayers/components/IPAdapter/IPAdapterMethod'; import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext'; +import { useIsSavingCanvas, usePullBboxIntoIPAdapter } from 'features/controlLayers/hooks/saveCanvasHooks'; import { ipaBeginEndStepPctChanged, ipaCLIPVisionModelChanged, @@ -18,12 +19,15 @@ import { selectCanvasSlice, selectEntityOrThrow } from 'features/controlLayers/s import type { CLIPVisionModelV2, IPMethodV2 } from 'features/controlLayers/store/types'; import type { IPAImageDropData } from 'features/dnd/types'; import { memo, useCallback, useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; +import { PiBoundingBoxBold } from 'react-icons/pi'; import type { ImageDTO, IPAdapterModelConfig, IPALayerImagePostUploadAction } from 'services/api/types'; import { IPAdapterImagePreview } from './IPAdapterImagePreview'; import { IPAdapterModel } from './IPAdapterModel'; export const IPAdapterSettings = memo(() => { + const { t } = useTranslation(); const dispatch = useAppDispatch(); const entityIdentifier = useEntityIdentifierContext('ip_adapter'); const selectIPAdapter = useMemo( @@ -82,6 +86,8 @@ export const IPAdapterSettings = memo(() => { () => ({ type: 'SET_IPA_IMAGE', id: entityIdentifier.id }), [entityIdentifier.id] ); + const pullBboxIntoIPAdapter = usePullBboxIntoIPAdapter(entityIdentifier); + const isSaving = useIsSavingCanvas(); return ( @@ -95,6 +101,14 @@ export const IPAdapterSettings = memo(() => { onChangeCLIPVisionModel={onChangeCLIPVisionModel} /> + } + /> diff --git a/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RegionalGuidanceIPAdapterSettings.tsx b/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RegionalGuidanceIPAdapterSettings.tsx index b11cc933bd9..ca6f8880973 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RegionalGuidanceIPAdapterSettings.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RegionalGuidanceIPAdapterSettings.tsx @@ -7,6 +7,10 @@ import { IPAdapterImagePreview } from 'features/controlLayers/components/IPAdapt import { IPAdapterMethod } from 'features/controlLayers/components/IPAdapter/IPAdapterMethod'; import { IPAdapterModel } from 'features/controlLayers/components/IPAdapter/IPAdapterModel'; import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext'; +import { + useIsSavingCanvas, + usePullBboxIntoRegionalGuidanceIPAdapter, +} from 'features/controlLayers/hooks/saveCanvasHooks'; import { rgIPAdapterBeginEndStepPctChanged, rgIPAdapterCLIPVisionModelChanged, @@ -20,7 +24,8 @@ import { selectCanvasSlice, selectRegionalGuidanceIPAdapter } from 'features/con import type { CLIPVisionModelV2, IPMethodV2 } from 'features/controlLayers/store/types'; import type { RGIPAdapterImageDropData } from 'features/dnd/types'; import { memo, useCallback, useMemo } from 'react'; -import { PiTrashSimpleBold } from 'react-icons/pi'; +import { useTranslation } from 'react-i18next'; +import { PiBoundingBoxBold, PiTrashSimpleBold } from 'react-icons/pi'; import type { ImageDTO, IPAdapterModelConfig, RGIPAdapterImagePostUploadAction } from 'services/api/types'; import { assert } from 'tsafe'; @@ -31,6 +36,7 @@ type Props = { export const RegionalGuidanceIPAdapterSettings = memo(({ ipAdapterId, ipAdapterNumber }: Props) => { const entityIdentifier = useEntityIdentifierContext('regional_guidance'); + const { t } = useTranslation(); const dispatch = useAppDispatch(); const onDeleteIPAdapter = useCallback(() => { dispatch(rgIPAdapterDeleted({ entityIdentifier, ipAdapterId })); @@ -100,6 +106,8 @@ export const RegionalGuidanceIPAdapterSettings = memo(({ ipAdapterId, ipAdapterN () => ({ type: 'SET_RG_IP_ADAPTER_IMAGE', id: entityIdentifier.id, ipAdapterId }), [entityIdentifier.id, ipAdapterId] ); + const pullBboxIntoIPAdapter = usePullBboxIntoRegionalGuidanceIPAdapter(entityIdentifier, ipAdapterId); + const isSaving = useIsSavingCanvas(); return ( @@ -125,6 +133,14 @@ export const RegionalGuidanceIPAdapterSettings = memo(({ ipAdapterId, ipAdapterN onChangeCLIPVisionModel={onChangeCLIPVisionModel} /> + } + /> diff --git a/invokeai/frontend/web/src/features/controlLayers/hooks/saveCanvasHooks.ts b/invokeai/frontend/web/src/features/controlLayers/hooks/saveCanvasHooks.ts index 8b1c907ddee..d1037e642cd 100644 --- a/invokeai/frontend/web/src/features/controlLayers/hooks/saveCanvasHooks.ts +++ b/invokeai/frontend/web/src/features/controlLayers/hooks/saveCanvasHooks.ts @@ -5,9 +5,18 @@ import { isOk, withResultAsync } from 'common/util/result'; import { useCanvasManager } from 'features/controlLayers/contexts/CanvasManagerProviderGate'; import { selectDefaultControlAdapter, selectDefaultIPAdapter } from 'features/controlLayers/hooks/addLayerHooks'; import { getPrefixedId } from 'features/controlLayers/konva/util'; -import { controlLayerAdded, ipaAdded, rasterLayerAdded, rgAdded } from 'features/controlLayers/store/canvasSlice'; +import { + controlLayerAdded, + entityRasterized, + ipaAdded, + ipaImageChanged, + rasterLayerAdded, + rgAdded, + rgIPAdapterImageChanged, +} from 'features/controlLayers/store/canvasSlice'; import type { CanvasControlLayerState, + CanvasEntityIdentifier, CanvasIPAdapterState, CanvasRasterLayerState, CanvasRegionalGuidanceState, @@ -102,7 +111,7 @@ export const useSaveBboxAsRegionalGuidanceIPAdapter = () => { dispatch(rgAdded({ overrides, isSelected: true })); }; - return { region: 'bbox', saveToGallery: true, onSave }; + return { region: 'bbox', saveToGallery: false, onSave }; }, [defaultIPAdapter, dispatch]); const saveBboxAsRegionalGuidanceIPAdapter = useSaveCanvas(saveBboxAsRegionalGuidanceIPAdapterArg); return saveBboxAsRegionalGuidanceIPAdapter; @@ -123,7 +132,7 @@ export const useSaveBboxAsGlobalIPAdapter = () => { dispatch(ipaAdded({ overrides, isSelected: true })); }; - return { region: 'bbox', saveToGallery: true, onSave }; + return { region: 'bbox', saveToGallery: false, onSave }; }, [defaultIPAdapter, dispatch]); const saveBboxAsIPAdapter = useSaveCanvas(saveBboxAsIPAdapterArg); return saveBboxAsIPAdapter; @@ -140,7 +149,7 @@ export const useSaveBboxAsRasterLayer = () => { dispatch(rasterLayerAdded({ overrides, isSelected: true })); }; - return { region: 'bbox', saveToGallery: true, onSave }; + return { region: 'bbox', saveToGallery: false, onSave }; }, [dispatch]); const saveBboxAsRasterLayer = useSaveCanvas(saveBboxAsRasterLayerArg); return saveBboxAsRasterLayer; @@ -160,8 +169,63 @@ export const useSaveBboxAsControlLayer = () => { dispatch(controlLayerAdded({ overrides, isSelected: true })); }; - return { region: 'bbox', saveToGallery: true, onSave }; + return { region: 'bbox', saveToGallery: false, onSave }; }, [defaultControlAdapter, dispatch]); const saveBboxAsControlLayer = useSaveCanvas(saveBboxAsControlLayerArg); return saveBboxAsControlLayer; }; + +export const usePullBboxIntoLayer = (entityIdentifier: CanvasEntityIdentifier<'control_layer' | 'raster_layer'>) => { + const dispatch = useAppDispatch(); + + const pullBboxIntoLayerArg = useMemo(() => { + const onSave = (imageDTO: ImageDTO, rect: Rect) => { + dispatch( + entityRasterized({ + entityIdentifier, + position: { x: rect.x, y: rect.y }, + imageObject: imageDTOToImageObject(imageDTO), + replaceObjects: true, + }) + ); + }; + + return { region: 'bbox', saveToGallery: false, onSave }; + }, [dispatch, entityIdentifier]); + + const pullBboxIntoLayer = useSaveCanvas(pullBboxIntoLayerArg); + return pullBboxIntoLayer; +}; + +export const usePullBboxIntoIPAdapter = (entityIdentifier: CanvasEntityIdentifier<'ip_adapter'>) => { + const dispatch = useAppDispatch(); + + const pullBboxIntoIPAdapterArg = useMemo(() => { + const onSave = (imageDTO: ImageDTO, _: Rect) => { + dispatch(ipaImageChanged({ entityIdentifier, imageDTO })); + }; + + return { region: 'bbox', saveToGallery: false, onSave }; + }, [dispatch, entityIdentifier]); + + const pullBboxIntoIPAdapter = useSaveCanvas(pullBboxIntoIPAdapterArg); + return pullBboxIntoIPAdapter; +}; + +export const usePullBboxIntoRegionalGuidanceIPAdapter = ( + entityIdentifier: CanvasEntityIdentifier<'regional_guidance'>, + ipAdapterId: string +) => { + const dispatch = useAppDispatch(); + + const pullBboxIntoRegionalGuidanceIPAdapterArg = useMemo(() => { + const onSave = (imageDTO: ImageDTO, _: Rect) => { + dispatch(rgIPAdapterImageChanged({ entityIdentifier, ipAdapterId, imageDTO })); + }; + + return { region: 'bbox', saveToGallery: false, onSave }; + }, [dispatch, entityIdentifier, ipAdapterId]); + + const pullBboxIntoRegionalGuidanceIPAdapter = useSaveCanvas(pullBboxIntoRegionalGuidanceIPAdapterArg); + return pullBboxIntoRegionalGuidanceIPAdapter; +};