diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts index 29df0bf542..c9dd883c6d 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts @@ -9,7 +9,6 @@ import { addBatchEnqueuedListener } from 'app/store/middleware/listenerMiddlewar import { addDeleteBoardAndImagesFulfilledListener } from 'app/store/middleware/listenerMiddleware/listeners/boardAndImagesDeleted'; import { addBoardIdSelectedListener } from 'app/store/middleware/listenerMiddleware/listeners/boardIdSelected'; import { addBulkDownloadListeners } from 'app/store/middleware/listenerMiddleware/listeners/bulkDownload'; -import { addControlAdapterPreprocessor } from 'app/store/middleware/listenerMiddleware/listeners/controlAdapterPreprocessor'; import { addEnqueueRequestedLinear } from 'app/store/middleware/listenerMiddleware/listeners/enqueueRequestedLinear'; import { addEnqueueRequestedNodes } from 'app/store/middleware/listenerMiddleware/listeners/enqueueRequestedNodes'; import { addGalleryImageClickedListener } from 'app/store/middleware/listenerMiddleware/listeners/galleryImageClicked'; @@ -133,4 +132,4 @@ addAdHocPostProcessingRequestedListener(startAppListening); addDynamicPromptsListener(startAppListening); addSetDefaultSettingsListener(startAppListening); -addControlAdapterPreprocessor(startAppListening); +// addControlAdapterPreprocessor(startAppListening); diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/boardAndImagesDeleted.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/boardAndImagesDeleted.ts index d3f20971b7..f5c3a95537 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/boardAndImagesDeleted.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/boardAndImagesDeleted.ts @@ -1,5 +1,5 @@ import type { AppStartListening } from 'app/store/middleware/listenerMiddleware'; -import { caAllDeleted, ipaAllDeleted, layerAllDeleted } from 'features/controlLayers/store/canvasV2Slice'; +import { ipaAllDeleted, layerAllDeleted } from 'features/controlLayers/store/canvasV2Slice'; import { getImageUsage } from 'features/deleteImageModal/store/selectors'; import { nodeEditorReset } from 'features/nodes/store/nodesSlice'; import { imagesApi } from 'services/api/endpoints/images'; @@ -14,7 +14,7 @@ export const addDeleteBoardAndImagesFulfilledListener = (startAppListening: AppS let wereLayersReset = false; let wasNodeEditorReset = false; - let wereControlAdaptersReset = false; + const wereControlAdaptersReset = false; let wereIPAdaptersReset = false; const { nodes, canvasV2 } = getState(); @@ -31,10 +31,10 @@ export const addDeleteBoardAndImagesFulfilledListener = (startAppListening: AppS wasNodeEditorReset = true; } - if (imageUsage.isControlAdapterImage && !wereControlAdaptersReset) { - dispatch(caAllDeleted()); - wereControlAdaptersReset = true; - } + // if (imageUsage.isControlAdapterImage && !wereControlAdaptersReset) { + // dispatch(caAllDeleted()); + // wereControlAdaptersReset = true; + // } if (imageUsage.isIPAdapterImage && !wereIPAdaptersReset) { dispatch(ipaAllDeleted()); diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageDeletionListeners.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageDeletionListeners.ts index 4d57cd6b02..17e6596701 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageDeletionListeners.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageDeletionListeners.ts @@ -1,12 +1,7 @@ import { logger } from 'app/logging/logger'; import type { AppStartListening } from 'app/store/middleware/listenerMiddleware'; import type { AppDispatch, RootState } from 'app/store/store'; -import { - caImageChanged, - caProcessedImageChanged, - entityDeleted, - ipaImageChanged, -} from 'features/controlLayers/store/canvasV2Slice'; +import { entityDeleted, ipaImageChanged } from 'features/controlLayers/store/canvasV2Slice'; import { imageDeletionConfirmed } from 'features/deleteImageModal/store/actions'; import { isModalOpenChanged } from 'features/deleteImageModal/store/slice'; import { selectListImagesQueryArgs } from 'features/gallery/store/gallerySelectors'; @@ -39,14 +34,17 @@ const deleteNodesImages = (state: RootState, dispatch: AppDispatch, imageDTO: Im }); }; -const deleteControlAdapterImages = (state: RootState, dispatch: AppDispatch, imageDTO: ImageDTO) => { - state.canvasV2.controlAdapters.entities.forEach(({ id, imageObject, processedImageObject }) => { - if (imageObject?.image.image_name === imageDTO.image_name || processedImageObject?.image.image_name === imageDTO.image_name) { - dispatch(caImageChanged({ id, imageDTO: null })); - dispatch(caProcessedImageChanged({ id, imageDTO: null })); - } - }); -}; +// const deleteControlAdapterImages = (state: RootState, dispatch: AppDispatch, imageDTO: ImageDTO) => { +// state.canvasV2.controlAdapters.entities.forEach(({ id, imageObject, processedImageObject }) => { +// if ( +// imageObject?.image.image_name === imageDTO.image_name || +// processedImageObject?.image.image_name === imageDTO.image_name +// ) { +// dispatch(caImageChanged({ id, imageDTO: null })); +// dispatch(caProcessedImageChanged({ id, imageDTO: null })); +// } +// }); +// }; const deleteIPAdapterImages = (state: RootState, dispatch: AppDispatch, imageDTO: ImageDTO) => { state.canvasV2.ipAdapters.entities.forEach(({ id, imageObject }) => { @@ -120,7 +118,7 @@ export const addImageDeletionListeners = (startAppListening: AppStartListening) } deleteNodesImages(state, dispatch, imageDTO); - deleteControlAdapterImages(state, dispatch, imageDTO); + // deleteControlAdapterImages(state, dispatch, imageDTO); deleteIPAdapterImages(state, dispatch, imageDTO); deleteLayerImages(state, dispatch, imageDTO); } catch { @@ -161,7 +159,7 @@ export const addImageDeletionListeners = (startAppListening: AppStartListening) imageDTOs.forEach((imageDTO) => { deleteNodesImages(state, dispatch, imageDTO); - deleteControlAdapterImages(state, dispatch, imageDTO); + // deleteControlAdapterImages(state, dispatch, imageDTO); deleteIPAdapterImages(state, dispatch, imageDTO); deleteLayerImages(state, dispatch, imageDTO); }); diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageDropped.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageDropped.ts index 80c1f2bebd..39bb31ce63 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageDropped.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageDropped.ts @@ -3,7 +3,6 @@ import { logger } from 'app/logging/logger'; import type { AppStartListening } from 'app/store/middleware/listenerMiddleware'; import { parseify } from 'common/util/serialize'; import { - caImageChanged, ipaImageChanged, layerAdded, rgIPAdapterImageChanged, @@ -60,18 +59,18 @@ export const addImageDroppedListener = (startAppListening: AppStartListening) => return; } - /** - * Image dropped on Control Adapter Layer - */ - if ( - overData.actionType === 'SET_CA_IMAGE' && - activeData.payloadType === 'IMAGE_DTO' && - activeData.payload.imageDTO - ) { - const { id } = overData.context; - dispatch(caImageChanged({ id, imageDTO: activeData.payload.imageDTO })); - return; - } + // /** + // * Image dropped on Control Adapter Layer + // */ + // if ( + // overData.actionType === 'SET_CA_IMAGE' && + // activeData.payloadType === 'IMAGE_DTO' && + // activeData.payload.imageDTO + // ) { + // const { id } = overData.context; + // dispatch(caImageChanged({ id, imageDTO: activeData.payload.imageDTO })); + // return; + // } /** * Image dropped on IP Adapter Layer diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageUploaded.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageUploaded.ts index fa23cdfc06..b0b26fbd1a 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageUploaded.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageUploaded.ts @@ -1,6 +1,6 @@ import { logger } from 'app/logging/logger'; import type { AppStartListening } from 'app/store/middleware/listenerMiddleware'; -import { caImageChanged, ipaImageChanged, rgIPAdapterImageChanged } from 'features/controlLayers/store/canvasV2Slice'; +import { ipaImageChanged, rgIPAdapterImageChanged } from 'features/controlLayers/store/canvasV2Slice'; import { selectListBoardsQueryArgs } from 'features/gallery/store/gallerySelectors'; import { fieldImageValueChanged } from 'features/nodes/store/nodesSlice'; import { upscaleInitialImageChanged } from 'features/parameters/store/upscaleSlice'; @@ -79,12 +79,12 @@ export const addImageUploadedFulfilledListener = (startAppListening: AppStartLis return; } - if (postUploadAction?.type === 'SET_CA_IMAGE') { - const { id } = postUploadAction; - dispatch(caImageChanged({ id, imageDTO })); - toast({ ...DEFAULT_UPLOADED_TOAST, description: t('toast.setControlImage') }); - return; - } + // if (postUploadAction?.type === 'SET_CA_IMAGE') { + // const { id } = postUploadAction; + // dispatch(caImageChanged({ id, imageDTO })); + // toast({ ...DEFAULT_UPLOADED_TOAST, description: t('toast.setControlImage') }); + // return; + // } if (postUploadAction?.type === 'SET_IPA_IMAGE') { const { id } = postUploadAction; diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/modelSelected.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/modelSelected.ts index e07dcdc844..bbb9cd1cef 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/modelSelected.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/modelSelected.ts @@ -1,7 +1,6 @@ import { logger } from 'app/logging/logger'; import type { AppStartListening } from 'app/store/middleware/listenerMiddleware'; import { - entityIsEnabledToggled, loraDeleted, modelChanged, vaeSelected, @@ -50,14 +49,14 @@ export const addModelSelectedListener = (startAppListening: AppStartListening) = } // handle incompatible controlnets - state.canvasV2.controlAdapters.entities.forEach((ca) => { - if (ca.model?.base !== newBaseModel) { - modelsCleared += 1; - if (ca.isEnabled) { - dispatch(entityIsEnabledToggled({ entityIdentifier: { id: ca.id, type: 'control_adapter' } })); - } - } - }); + // state.canvasV2.controlAdapters.entities.forEach((ca) => { + // if (ca.model?.base !== newBaseModel) { + // modelsCleared += 1; + // if (ca.isEnabled) { + // dispatch(entityIsEnabledToggled({ entityIdentifier: { id: ca.id, type: 'control_adapter' } })); + // } + // } + // }); if (modelsCleared > 0) { toast({ diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/modelsLoaded.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/modelsLoaded.ts index 87967544a8..86daeab55a 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/modelsLoaded.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/modelsLoaded.ts @@ -5,7 +5,6 @@ import type { JSONObject } from 'common/types'; import { bboxHeightChanged, bboxWidthChanged, - caModelChanged, ipaModelChanged, loraDeleted, modelChanged, @@ -21,7 +20,6 @@ import type { Logger } from 'roarr'; import { modelConfigsAdapterSelectors, modelsApi } from 'services/api/endpoints/models'; import type { AnyModelConfig } from 'services/api/types'; import { - isControlNetOrT2IAdapterModelConfig, isIPAdapterModelConfig, isLoRAModelConfig, isNonRefinerMainModelConfig, @@ -171,14 +169,14 @@ const handleLoRAModels: ModelHandler = (models, state, dispatch, _log) => { }; const handleControlAdapterModels: ModelHandler = (models, state, dispatch, _log) => { - const caModels = models.filter(isControlNetOrT2IAdapterModelConfig); - state.canvasV2.controlAdapters.entities.forEach((ca) => { - const isModelAvailable = caModels.some((m) => m.key === ca.model?.key); - if (isModelAvailable) { - return; - } - dispatch(caModelChanged({ id: ca.id, modelConfig: null })); - }); + // const caModels = models.filter(isControlNetOrT2IAdapterModelConfig); + // state.canvasV2.controlAdapters.entities.forEach((ca) => { + // const isModelAvailable = caModels.some((m) => m.key === ca.model?.key); + // if (isModelAvailable) { + // return; + // } + // dispatch(caModelChanged({ id: ca.id, modelConfig: null })); + // }); }; const handleIPAdapterModels: ModelHandler = (models, state, dispatch, _log) => { diff --git a/invokeai/frontend/web/src/common/hooks/useIsReadyToEnqueue.ts b/invokeai/frontend/web/src/common/hooks/useIsReadyToEnqueue.ts index bf8bf22d08..48df41fc48 100644 --- a/invokeai/frontend/web/src/common/hooks/useIsReadyToEnqueue.ts +++ b/invokeai/frontend/web/src/common/hooks/useIsReadyToEnqueue.ts @@ -125,41 +125,41 @@ const createSelector = (templates: Templates) => reasons.push({ content: i18n.t('parameters.invoke.noModelSelected') }); } - canvasV2.controlAdapters.entities - .filter((ca) => ca.isEnabled) - .forEach((ca, i) => { - const layerLiteral = i18n.t('controlLayers.layers_one'); - const layerNumber = i + 1; - const layerType = i18n.t(LAYER_TYPE_TO_TKEY[ca.type]); - const prefix = `${layerLiteral} #${layerNumber} (${layerType})`; - const problems: string[] = []; - // Must have model - if (!ca.model) { - problems.push(i18n.t('parameters.invoke.layer.controlAdapterNoModelSelected')); - } - // Model base must match - if (ca.model?.base !== model?.base) { - problems.push(i18n.t('parameters.invoke.layer.controlAdapterIncompatibleBaseModel')); - } - // Must have a control image OR, if it has a processor, it must have a processed image - if (!ca.imageObject) { - problems.push(i18n.t('parameters.invoke.layer.controlAdapterNoImageSelected')); - } else if (ca.processorConfig && !ca.processedImageObject) { - problems.push(i18n.t('parameters.invoke.layer.controlAdapterImageNotProcessed')); - } - // T2I Adapters require images have dimensions that are multiples of 64 (SD1.5) or 32 (SDXL) - if (ca.adapterType === 't2i_adapter') { - const multiple = model?.base === 'sdxl' ? 32 : 64; - if (bbox.rect.width % multiple !== 0 || bbox.rect.height % multiple !== 0) { - problems.push(i18n.t('parameters.invoke.layer.t2iAdapterIncompatibleDimensions', { multiple })); - } - } + // canvasV2.controlAdapters.entities + // .filter((ca) => ca.isEnabled) + // .forEach((ca, i) => { + // const layerLiteral = i18n.t('controlLayers.layers_one'); + // const layerNumber = i + 1; + // const layerType = i18n.t(LAYER_TYPE_TO_TKEY[ca.type]); + // const prefix = `${layerLiteral} #${layerNumber} (${layerType})`; + // const problems: string[] = []; + // // Must have model + // if (!ca.model) { + // problems.push(i18n.t('parameters.invoke.layer.controlAdapterNoModelSelected')); + // } + // // Model base must match + // if (ca.model?.base !== model?.base) { + // problems.push(i18n.t('parameters.invoke.layer.controlAdapterIncompatibleBaseModel')); + // } + // // Must have a control image OR, if it has a processor, it must have a processed image + // if (!ca.imageObject) { + // problems.push(i18n.t('parameters.invoke.layer.controlAdapterNoImageSelected')); + // } else if (ca.processorConfig && !ca.processedImageObject) { + // problems.push(i18n.t('parameters.invoke.layer.controlAdapterImageNotProcessed')); + // } + // // T2I Adapters require images have dimensions that are multiples of 64 (SD1.5) or 32 (SDXL) + // if (ca.adapterType === 't2i_adapter') { + // const multiple = model?.base === 'sdxl' ? 32 : 64; + // if (bbox.rect.width % multiple !== 0 || bbox.rect.height % multiple !== 0) { + // problems.push(i18n.t('parameters.invoke.layer.t2iAdapterIncompatibleDimensions', { multiple })); + // } + // } - if (problems.length) { - const content = upperFirst(problems.join(', ')); - reasons.push({ prefix, content }); - } - }); + // if (problems.length) { + // const content = upperFirst(problems.join(', ')); + // reasons.push({ prefix, content }); + // } + // }); canvasV2.ipAdapters.entities .filter((ipa) => ipa.isEnabled) diff --git a/invokeai/frontend/web/src/features/controlLayers/components/CanvasEntityList.tsx b/invokeai/frontend/web/src/features/controlLayers/components/CanvasEntityList.tsx index 9b3c9d3bc7..edb1708aae 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/CanvasEntityList.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/CanvasEntityList.tsx @@ -1,6 +1,5 @@ import { Flex } from '@invoke-ai/ui-library'; import ScrollableContent from 'common/components/OverlayScrollbars/ScrollableContent'; -import { ControlAdapterList } from 'features/controlLayers/components/ControlAdapter/ControlAdapterList'; import { InpaintMask } from 'features/controlLayers/components/InpaintMask/InpaintMask'; import { IPAdapterList } from 'features/controlLayers/components/IPAdapter/IPAdapterList'; import { LayerEntityList } from 'features/controlLayers/components/Layer/LayerEntityList'; @@ -13,7 +12,6 @@ export const CanvasEntityList = memo(() => { - diff --git a/invokeai/frontend/web/src/features/controlLayers/components/ControlAdapter/ControlAdapter.tsx b/invokeai/frontend/web/src/features/controlLayers/components/ControlAdapter/ControlAdapter.tsx deleted file mode 100644 index 280fd74288..0000000000 --- a/invokeai/frontend/web/src/features/controlLayers/components/ControlAdapter/ControlAdapter.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { Spacer, useDisclosure } from '@invoke-ai/ui-library'; -import { CanvasEntityContainer } from 'features/controlLayers/components/common/CanvasEntityContainer'; -import { CanvasEntityDeleteButton } from 'features/controlLayers/components/common/CanvasEntityDeleteButton'; -import { CanvasEntityEnabledToggle } from 'features/controlLayers/components/common/CanvasEntityEnabledToggle'; -import { CanvasEntityHeader } from 'features/controlLayers/components/common/CanvasEntityHeader'; -import { CanvasEntityTitle } from 'features/controlLayers/components/common/CanvasEntityTitle'; -import { ControlAdapterActionsMenu } from 'features/controlLayers/components/ControlAdapter/ControlAdapterActionsMenu'; -import { ControlAdapterOpacityAndFilter } from 'features/controlLayers/components/ControlAdapter/ControlAdapterOpacityAndFilter'; -import { ControlAdapterSettings } from 'features/controlLayers/components/ControlAdapter/ControlAdapterSettings'; -import { EntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext'; -import type { CanvasEntityIdentifier } from 'features/controlLayers/store/types'; -import { memo, useMemo } from 'react'; - -type Props = { - id: string; -}; - -export const ControlAdapter = memo(({ id }: Props) => { - const entityIdentifier = useMemo(() => ({ id, type: 'control_adapter' }), [id]); - const { isOpen, onToggle } = useDisclosure({ defaultIsOpen: true }); - - return ( - - - - - - - - - - - {isOpen && } - - - ); -}); - -ControlAdapter.displayName = 'ControlAdapter'; diff --git a/invokeai/frontend/web/src/features/controlLayers/components/ControlAdapter/ControlAdapterActionsMenu.tsx b/invokeai/frontend/web/src/features/controlLayers/components/ControlAdapter/ControlAdapterActionsMenu.tsx deleted file mode 100644 index a9b5815a95..0000000000 --- a/invokeai/frontend/web/src/features/controlLayers/components/ControlAdapter/ControlAdapterActionsMenu.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { Menu, MenuList } from '@invoke-ai/ui-library'; -import { CanvasEntityActionMenuItems } from 'features/controlLayers/components/common/CanvasEntityActionMenuItems'; -import { CanvasEntityMenuButton } from 'features/controlLayers/components/common/CanvasEntityMenuButton'; -import { memo } from 'react'; - -export const ControlAdapterActionsMenu = memo(() => { - return ( - - - - - - - ); -}); - -ControlAdapterActionsMenu.displayName = 'ControlAdapterActionsMenu'; diff --git a/invokeai/frontend/web/src/features/controlLayers/components/ControlAdapter/ControlAdapterImagePreview.tsx b/invokeai/frontend/web/src/features/controlLayers/components/ControlAdapter/ControlAdapterImagePreview.tsx deleted file mode 100644 index 8faab37780..0000000000 --- a/invokeai/frontend/web/src/features/controlLayers/components/ControlAdapter/ControlAdapterImagePreview.tsx +++ /dev/null @@ -1,229 +0,0 @@ -import { Box, Flex, Spinner, useShiftModifier } from '@invoke-ai/ui-library'; -import { skipToken } from '@reduxjs/toolkit/query'; -import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import IAIDndImage from 'common/components/IAIDndImage'; -import IAIDndImageIcon from 'common/components/IAIDndImageIcon'; -import { bboxHeightChanged, bboxWidthChanged } from 'features/controlLayers/store/canvasV2Slice'; -import { selectOptimalDimension } from 'features/controlLayers/store/selectors'; -import type { CanvasControlAdapterState } from 'features/controlLayers/store/types'; -import type { ImageDraggableData, TypesafeDroppableData } from 'features/dnd/types'; -import { calculateNewSize } from 'features/parameters/components/DocumentSize/calculateNewSize'; -import { memo, useCallback, useEffect, useMemo, useState } from 'react'; -import { useTranslation } from 'react-i18next'; -import { PiArrowCounterClockwiseBold, PiFloppyDiskBold, PiRulerBold } from 'react-icons/pi'; -import { - useAddImageToBoardMutation, - useChangeImageIsIntermediateMutation, - useGetImageDTOQuery, - useRemoveImageFromBoardMutation, -} from 'services/api/endpoints/images'; -import type { ImageDTO, PostUploadAction } from 'services/api/types'; - -type Props = { - controlAdapter: CanvasControlAdapterState; - onChangeImage: (imageDTO: ImageDTO | null) => void; - droppableData: TypesafeDroppableData; - postUploadAction: PostUploadAction; - onErrorLoadingImage: () => void; - onErrorLoadingProcessedImage: () => void; -}; - -export const ControlAdapterImagePreview = memo( - ({ - controlAdapter, - onChangeImage, - droppableData, - postUploadAction, - onErrorLoadingImage, - onErrorLoadingProcessedImage, - }: Props) => { - const { t } = useTranslation(); - const dispatch = useAppDispatch(); - const autoAddBoardId = useAppSelector((s) => s.gallery.autoAddBoardId); - const isConnected = useAppSelector((s) => s.system.isConnected); - const optimalDimension = useAppSelector(selectOptimalDimension); - const shift = useShiftModifier(); - - const [isMouseOverImage, setIsMouseOverImage] = useState(false); - - const { currentData: controlImage, isError: isErrorControlImage } = useGetImageDTOQuery( - controlAdapter.imageObject?.image.image_name ?? skipToken - ); - const { currentData: processedControlImage, isError: isErrorProcessedControlImage } = useGetImageDTOQuery( - controlAdapter.processedImageObject?.image.image_name ?? skipToken - ); - - const [changeIsIntermediate] = useChangeImageIsIntermediateMutation(); - const [addToBoard] = useAddImageToBoardMutation(); - const [removeFromBoard] = useRemoveImageFromBoardMutation(); - const handleResetControlImage = useCallback(() => { - onChangeImage(null); - }, [onChangeImage]); - - const handleSaveControlImage = useCallback(async () => { - if (!processedControlImage) { - return; - } - - await changeIsIntermediate({ - imageDTO: processedControlImage, - is_intermediate: false, - }).unwrap(); - - if (autoAddBoardId !== 'none') { - addToBoard({ - imageDTO: processedControlImage, - board_id: autoAddBoardId, - }); - } else { - removeFromBoard({ imageDTO: processedControlImage }); - } - }, [processedControlImage, changeIsIntermediate, autoAddBoardId, addToBoard, removeFromBoard]); - - const handleSetControlImageToDimensions = useCallback(() => { - if (!controlImage) { - return; - } - - const options = { updateAspectRatio: true, clamp: true }; - - if (shift) { - const { width, height } = controlImage; - dispatch(bboxWidthChanged({ width, ...options })); - dispatch(bboxHeightChanged({ height, ...options })); - } else { - const { width, height } = calculateNewSize( - controlImage.width / controlImage.height, - optimalDimension * optimalDimension - ); - dispatch(bboxWidthChanged({ width, ...options })); - dispatch(bboxHeightChanged({ height, ...options })); - } - }, [controlImage, dispatch, optimalDimension, shift]); - - const handleMouseEnter = useCallback(() => { - setIsMouseOverImage(true); - }, []); - - const handleMouseLeave = useCallback(() => { - setIsMouseOverImage(false); - }, []); - - const draggableData = useMemo(() => { - if (controlImage) { - return { - id: controlAdapter.id, - payloadType: 'IMAGE_DTO', - payload: { imageDTO: controlImage }, - }; - } - }, [controlImage, controlAdapter.id]); - - const shouldShowProcessedImage = - controlImage && - processedControlImage && - !isMouseOverImage && - !controlAdapter.processorPendingBatchId && - controlAdapter.processorConfig !== null; - - useEffect(() => { - if (!isConnected) { - return; - } - if (isErrorControlImage) { - onErrorLoadingImage(); - } - if (isErrorProcessedControlImage) { - onErrorLoadingProcessedImage(); - } - }, [ - handleResetControlImage, - isConnected, - isErrorControlImage, - isErrorProcessedControlImage, - onErrorLoadingImage, - onErrorLoadingProcessedImage, - ]); - - return ( - - - - - - - - {controlImage && ( - - } - tooltip={t('controlnet.resetControlImage')} - /> - } - tooltip={t('controlnet.saveControlImage')} - /> - } - tooltip={ - shift ? t('controlnet.setControlImageDimensionsForce') : t('controlnet.setControlImageDimensions') - } - /> - - )} - - {controlAdapter.processorPendingBatchId !== null && ( - - - - )} - - ); - } -); - -ControlAdapterImagePreview.displayName = 'ControlAdapterImagePreview'; diff --git a/invokeai/frontend/web/src/features/controlLayers/components/ControlAdapter/ControlAdapterList.tsx b/invokeai/frontend/web/src/features/controlLayers/components/ControlAdapter/ControlAdapterList.tsx deleted file mode 100644 index 694dade9a8..0000000000 --- a/invokeai/frontend/web/src/features/controlLayers/components/ControlAdapter/ControlAdapterList.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; -import { useAppSelector } from 'app/store/storeHooks'; -import { CanvasEntityGroupTitle } from 'features/controlLayers/components/common/CanvasEntityGroupTitle'; -import { ControlAdapter } from 'features/controlLayers/components/ControlAdapter/ControlAdapter'; -import { mapId } from 'features/controlLayers/konva/util'; -import { selectCanvasV2Slice } from 'features/controlLayers/store/canvasV2Slice'; -import { memo } from 'react'; -import { useTranslation } from 'react-i18next'; - -const selectEntityIds = createMemoizedSelector(selectCanvasV2Slice, (canvasV2) => { - return canvasV2.controlAdapters.entities.map(mapId).reverse(); -}); - -export const ControlAdapterList = memo(() => { - const { t } = useTranslation(); - const isSelected = useAppSelector((s) => Boolean(s.canvasV2.selectedEntityIdentifier?.type === 'control_adapter')); - const caIds = useAppSelector(selectEntityIds); - - if (caIds.length === 0) { - return null; - } - - if (caIds.length > 0) { - return ( - <> - - {caIds.map((id) => ( - - ))} - - ); - } -}); - -ControlAdapterList.displayName = 'ControlAdapterList'; diff --git a/invokeai/frontend/web/src/features/controlLayers/components/ControlAdapter/ControlAdapterOpacityAndFilter.tsx b/invokeai/frontend/web/src/features/controlLayers/components/ControlAdapter/ControlAdapterOpacityAndFilter.tsx deleted file mode 100644 index ba4f85a1e1..0000000000 --- a/invokeai/frontend/web/src/features/controlLayers/components/ControlAdapter/ControlAdapterOpacityAndFilter.tsx +++ /dev/null @@ -1,99 +0,0 @@ -import { - CompositeNumberInput, - CompositeSlider, - Flex, - FormControl, - FormLabel, - IconButton, - Popover, - PopoverArrow, - PopoverBody, - PopoverContent, - PopoverTrigger, - Switch, -} from '@invoke-ai/ui-library'; -import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import { stopPropagation } from 'common/util/stopPropagation'; -import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext'; -import { caFilterChanged, caOpacityChanged } from 'features/controlLayers/store/canvasV2Slice'; -import { selectCAOrThrow } from 'features/controlLayers/store/controlAdaptersReducers'; -import type { ChangeEvent } from 'react'; -import { memo, useCallback } from 'react'; -import { useTranslation } from 'react-i18next'; -import { PiDropHalfFill } from 'react-icons/pi'; - -const marks = [0, 25, 50, 75, 100]; -const formatPct = (v: number | string) => `${v} %`; - -export const ControlAdapterOpacityAndFilter = memo(() => { - const { id } = useEntityIdentifierContext(); - const { t } = useTranslation(); - const dispatch = useAppDispatch(); - const opacity = useAppSelector((s) => Math.round(selectCAOrThrow(s.canvasV2, id).opacity * 100)); - const isFilterEnabled = useAppSelector((s) => - selectCAOrThrow(s.canvasV2, id).filters.includes('LightnessToAlphaFilter') - ); - const onChangeOpacity = useCallback( - (v: number) => { - dispatch(caOpacityChanged({ id, opacity: v / 100 })); - }, - [dispatch, id] - ); - const onChangeFilter = useCallback( - (e: ChangeEvent) => { - dispatch(caFilterChanged({ id, filters: e.target.checked ? ['LightnessToAlphaFilter'] : [] })); - }, - [dispatch, id] - ); - return ( - - - } - variant="ghost" - onDoubleClick={stopPropagation} - /> - - - - - - - - {t('controlLayers.opacityFilter')} - - - - - {t('controlLayers.opacity')} - - - - - - - - ); -}); - -ControlAdapterOpacityAndFilter.displayName = 'ControlAdapterOpacityAndFilter'; diff --git a/invokeai/frontend/web/src/features/controlLayers/components/ControlAdapter/ControlAdapterProcessorConfig.tsx b/invokeai/frontend/web/src/features/controlLayers/components/ControlAdapter/ControlAdapterProcessorConfig.tsx deleted file mode 100644 index 6836584ba3..0000000000 --- a/invokeai/frontend/web/src/features/controlLayers/components/ControlAdapter/ControlAdapterProcessorConfig.tsx +++ /dev/null @@ -1,84 +0,0 @@ -import { CannyProcessor } from 'features/controlLayers/components/ControlAdapter/processors/CannyProcessor'; -import { ColorMapProcessor } from 'features/controlLayers/components/ControlAdapter/processors/ColorMapProcessor'; -import { ContentShuffleProcessor } from 'features/controlLayers/components/ControlAdapter/processors/ContentShuffleProcessor'; -import { DepthAnythingProcessor } from 'features/controlLayers/components/ControlAdapter/processors/DepthAnythingProcessor'; -import { DWOpenposeProcessor } from 'features/controlLayers/components/ControlAdapter/processors/DWOpenposeProcessor'; -import { HedProcessor } from 'features/controlLayers/components/ControlAdapter/processors/HedProcessor'; -import { LineartProcessor } from 'features/controlLayers/components/ControlAdapter/processors/LineartProcessor'; -import { MediapipeFaceProcessor } from 'features/controlLayers/components/ControlAdapter/processors/MediapipeFaceProcessor'; -import { MidasDepthProcessor } from 'features/controlLayers/components/ControlAdapter/processors/MidasDepthProcessor'; -import { MlsdImageProcessor } from 'features/controlLayers/components/ControlAdapter/processors/MlsdImageProcessor'; -import { PidiProcessor } from 'features/controlLayers/components/ControlAdapter/processors/PidiProcessor'; -import type { FilterConfig } from 'features/controlLayers/store/types'; -import { memo } from 'react'; - -type Props = { - config: FilterConfig | null; - onChange: (config: FilterConfig | null) => void; -}; - -export const ControlAdapterProcessorConfig = memo(({ config, onChange }: Props) => { - if (!config) { - return null; - } - - if (config.type === 'canny_image_processor') { - return ; - } - - if (config.type === 'color_map_image_processor') { - return ; - } - - if (config.type === 'depth_anything_image_processor') { - return ; - } - - if (config.type === 'hed_image_processor') { - return ; - } - - if (config.type === 'lineart_image_processor') { - return ; - } - - if (config.type === 'content_shuffle_image_processor') { - return ; - } - - if (config.type === 'lineart_anime_image_processor') { - // No configurable options for this processor - return null; - } - - if (config.type === 'mediapipe_face_processor') { - return ; - } - - if (config.type === 'midas_depth_image_processor') { - return ; - } - - if (config.type === 'mlsd_image_processor') { - return ; - } - - if (config.type === 'normalbae_image_processor') { - // No configurable options for this processor - return null; - } - - if (config.type === 'dw_openpose_image_processor') { - return ; - } - - if (config.type === 'pidi_image_processor') { - return ; - } - - if (config.type === 'zoe_depth_image_processor') { - return null; - } -}); - -ControlAdapterProcessorConfig.displayName = 'ControlAdapterProcessorConfig'; diff --git a/invokeai/frontend/web/src/features/controlLayers/components/ControlAdapter/ControlAdapterProcessorTypeSelect.tsx b/invokeai/frontend/web/src/features/controlLayers/components/ControlAdapter/ControlAdapterProcessorTypeSelect.tsx deleted file mode 100644 index e4d0d1878d..0000000000 --- a/invokeai/frontend/web/src/features/controlLayers/components/ControlAdapter/ControlAdapterProcessorTypeSelect.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import type { ComboboxOnChange } from '@invoke-ai/ui-library'; -import { Combobox, Flex, FormControl, FormLabel, IconButton } from '@invoke-ai/ui-library'; -import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; -import { useAppSelector } from 'app/store/storeHooks'; -import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover'; -import type {FilterConfig } from 'features/controlLayers/store/types'; -import { IMAGE_FILTERS, isFilterType } from 'features/controlLayers/store/types'; -import { configSelector } from 'features/system/store/configSelectors'; -import { includes, map } from 'lodash-es'; -import { memo, useCallback, useMemo } from 'react'; -import { useTranslation } from 'react-i18next'; -import { PiXBold } from 'react-icons/pi'; -import { assert } from 'tsafe'; - -type Props = { - config: FilterConfig | null; - onChange: (config: FilterConfig | null) => void; -}; - -const selectDisabledProcessors = createMemoizedSelector( - configSelector, - (config) => config.sd.disabledControlNetProcessors -); - -export const ControlAdapterProcessorTypeSelect = memo(({ config, onChange }: Props) => { - const { t } = useTranslation(); - const disabledProcessors = useAppSelector(selectDisabledProcessors); - const options = useMemo(() => { - return map(IMAGE_FILTERS, ({ labelTKey }, type) => ({ value: type, label: t(labelTKey) })).filter( - (o) => !includes(disabledProcessors, o.value) - ); - }, [disabledProcessors, t]); - - const _onChange = useCallback( - (v) => { - if (!v) { - onChange(null); - } else { - assert(isFilterType(v.value)); - onChange(IMAGE_FILTERS[v.value].buildDefaults()); - } - }, - [onChange] - ); - const clearProcessor = useCallback(() => { - onChange(null); - }, [onChange]); - const value = useMemo(() => options.find((o) => o.value === config?.type) ?? null, [options, config?.type]); - - return ( - - - - {t('controlnet.processor')} - - - - } - variant="ghost" - size="sm" - /> - - ); -}); - -ControlAdapterProcessorTypeSelect.displayName = 'ControlAdapterProcessorTypeSelect'; diff --git a/invokeai/frontend/web/src/features/controlLayers/components/ControlAdapter/ControlAdapterSettings.tsx b/invokeai/frontend/web/src/features/controlLayers/components/ControlAdapter/ControlAdapterSettings.tsx deleted file mode 100644 index ef2379d117..0000000000 --- a/invokeai/frontend/web/src/features/controlLayers/components/ControlAdapter/ControlAdapterSettings.tsx +++ /dev/null @@ -1,154 +0,0 @@ -import { Box, Divider, Flex, Icon, IconButton } from '@invoke-ai/ui-library'; -import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import { BeginEndStepPct } from 'features/controlLayers/components/common/BeginEndStepPct'; -import { CanvasEntitySettings } from 'features/controlLayers/components/common/CanvasEntitySettings'; -import { Weight } from 'features/controlLayers/components/common/Weight'; -import { ControlAdapterControlModeSelect } from 'features/controlLayers/components/ControlAdapter/ControlAdapterControlModeSelect'; -import { ControlAdapterImagePreview } from 'features/controlLayers/components/ControlAdapter/ControlAdapterImagePreview'; -import { ControlAdapterModel } from 'features/controlLayers/components/ControlAdapter/ControlAdapterModel'; -import { ControlAdapterProcessorConfig } from 'features/controlLayers/components/ControlAdapter/ControlAdapterProcessorConfig'; -import { ControlAdapterProcessorTypeSelect } from 'features/controlLayers/components/ControlAdapter/ControlAdapterProcessorTypeSelect'; -import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext'; -import { - caBeginEndStepPctChanged, - caControlModeChanged, - caImageChanged, - caModelChanged, - caProcessedImageChanged, - caProcessorConfigChanged, - caWeightChanged, -} from 'features/controlLayers/store/canvasV2Slice'; -import { selectCAOrThrow } from 'features/controlLayers/store/controlAdaptersReducers'; -import type { ControlModeV2, FilterConfig } from 'features/controlLayers/store/types'; -import type { CAImageDropData } from 'features/dnd/types'; -import { memo, useCallback, useMemo } from 'react'; -import { useTranslation } from 'react-i18next'; -import { PiCaretUpBold } from 'react-icons/pi'; -import { useToggle } from 'react-use'; -import type { - CAImagePostUploadAction, - ControlNetModelConfig, - ImageDTO, - T2IAdapterModelConfig, -} from 'services/api/types'; - -export const ControlAdapterSettings = memo(() => { - const { id } = useEntityIdentifierContext(); - const dispatch = useAppDispatch(); - const { t } = useTranslation(); - const [isExpanded, toggleIsExpanded] = useToggle(false); - - const controlAdapter = useAppSelector((s) => selectCAOrThrow(s.canvasV2, id)); - - const onChangeBeginEndStepPct = useCallback( - (beginEndStepPct: [number, number]) => { - dispatch(caBeginEndStepPctChanged({ id, beginEndStepPct })); - }, - [dispatch, id] - ); - - const onChangeControlMode = useCallback( - (controlMode: ControlModeV2) => { - dispatch(caControlModeChanged({ id, controlMode })); - }, - [dispatch, id] - ); - - const onChangeWeight = useCallback( - (weight: number) => { - dispatch(caWeightChanged({ id, weight })); - }, - [dispatch, id] - ); - - const onChangeProcessorConfig = useCallback( - (processorConfig: FilterConfig | null) => { - dispatch(caProcessorConfigChanged({ id, processorConfig })); - }, - [dispatch, id] - ); - - const onChangeModel = useCallback( - (modelConfig: ControlNetModelConfig | T2IAdapterModelConfig) => { - dispatch(caModelChanged({ id, modelConfig })); - }, - [dispatch, id] - ); - - const onChangeImage = useCallback( - (imageDTO: ImageDTO | null) => { - dispatch(caImageChanged({ id, imageDTO })); - }, - [dispatch, id] - ); - - const onErrorLoadingImage = useCallback(() => { - dispatch(caImageChanged({ id, imageDTO: null })); - }, [dispatch, id]); - - const onErrorLoadingProcessedImage = useCallback(() => { - dispatch(caProcessedImageChanged({ id, imageDTO: null })); - }, [dispatch, id]); - - const droppableData = useMemo(() => ({ actionType: 'SET_CA_IMAGE', context: { id }, id }), [id]); - const postUploadAction = useMemo(() => ({ id, type: 'SET_CA_IMAGE' }), [id]); - - return ( - - - - - - - - - } - /> - - - - {controlAdapter.adapterType === 'controlnet' && ( - - )} - - - - - - - - {isExpanded && ( - <> - - - - - - - )} - - - ); -}); - -ControlAdapterSettings.displayName = 'ControlAdapterSettings'; diff --git a/invokeai/frontend/web/src/features/controlLayers/components/ControlAdapter/processors/CannyProcessor.tsx b/invokeai/frontend/web/src/features/controlLayers/components/ControlAdapter/processors/CannyProcessor.tsx deleted file mode 100644 index d1b15c6645..0000000000 --- a/invokeai/frontend/web/src/features/controlLayers/components/ControlAdapter/processors/CannyProcessor.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library'; -import type { ProcessorComponentProps } from 'features/controlLayers/components/ControlAdapter/processors/types'; -import type { CannyProcessorConfig } from 'features/controlLayers/store/types'; -import { IMAGE_FILTERS } from 'features/controlLayers/store/types'; -import { useCallback } from 'react'; -import { useTranslation } from 'react-i18next'; - -import ProcessorWrapper from './ProcessorWrapper'; - -type Props = ProcessorComponentProps; -const DEFAULTS = IMAGE_FILTERS['canny_image_processor'].buildDefaults(); - -export const CannyProcessor = ({ onChange, config }: Props) => { - const { t } = useTranslation(); - const handleLowThresholdChanged = useCallback( - (v: number) => { - onChange({ ...config, low_threshold: v }); - }, - [onChange, config] - ); - const handleHighThresholdChanged = useCallback( - (v: number) => { - onChange({ ...config, high_threshold: v }); - }, - [onChange, config] - ); - - return ( - - - {t('controlnet.lowThreshold')} - - - - - {t('controlnet.highThreshold')} - - - - - ); -}; - -CannyProcessor.displayName = 'CannyProcessor'; diff --git a/invokeai/frontend/web/src/features/controlLayers/components/ControlAdapter/processors/ColorMapProcessor.tsx b/invokeai/frontend/web/src/features/controlLayers/components/ControlAdapter/processors/ColorMapProcessor.tsx deleted file mode 100644 index 3261703ded..0000000000 --- a/invokeai/frontend/web/src/features/controlLayers/components/ControlAdapter/processors/ColorMapProcessor.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library'; -import type { ProcessorComponentProps } from 'features/controlLayers/components/ControlAdapter/processors/types'; -import type { ColorMapProcessorConfig } from 'features/controlLayers/store/types'; -import { IMAGE_FILTERS } from 'features/controlLayers/store/types'; -import { memo, useCallback } from 'react'; -import { useTranslation } from 'react-i18next'; - -import ProcessorWrapper from './ProcessorWrapper'; - -type Props = ProcessorComponentProps; -const DEFAULTS = IMAGE_FILTERS['color_map_image_processor'].buildDefaults(); - -export const ColorMapProcessor = memo(({ onChange, config }: Props) => { - const { t } = useTranslation(); - const handleColorMapTileSizeChanged = useCallback( - (v: number) => { - onChange({ ...config, color_map_tile_size: v }); - }, - [config, onChange] - ); - - return ( - - - {t('controlnet.colorMapTileSize')} - - - - - ); -}); - -ColorMapProcessor.displayName = 'ColorMapProcessor'; diff --git a/invokeai/frontend/web/src/features/controlLayers/components/ControlAdapter/processors/ContentShuffleProcessor.tsx b/invokeai/frontend/web/src/features/controlLayers/components/ControlAdapter/processors/ContentShuffleProcessor.tsx deleted file mode 100644 index b86387097a..0000000000 --- a/invokeai/frontend/web/src/features/controlLayers/components/ControlAdapter/processors/ContentShuffleProcessor.tsx +++ /dev/null @@ -1,79 +0,0 @@ -import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library'; -import type { ProcessorComponentProps } from 'features/controlLayers/components/ControlAdapter/processors/types'; -import type { ContentShuffleProcessorConfig } from 'features/controlLayers/store/types'; -import { IMAGE_FILTERS } from 'features/controlLayers/store/types'; -import { memo, useCallback } from 'react'; -import { useTranslation } from 'react-i18next'; - -import ProcessorWrapper from './ProcessorWrapper'; - -type Props = ProcessorComponentProps; -const DEFAULTS = IMAGE_FILTERS['content_shuffle_image_processor'].buildDefaults(); - -export const ContentShuffleProcessor = memo(({ onChange, config }: Props) => { - const { t } = useTranslation(); - - const handleWChanged = useCallback( - (v: number) => { - onChange({ ...config, w: v }); - }, - [config, onChange] - ); - - const handleHChanged = useCallback( - (v: number) => { - onChange({ ...config, h: v }); - }, - [config, onChange] - ); - - const handleFChanged = useCallback( - (v: number) => { - onChange({ ...config, f: v }); - }, - [config, onChange] - ); - - return ( - - - {t('controlnet.w')} - - - - - {t('controlnet.h')} - - - - - {t('controlnet.f')} - - - - - ); -}); - -ContentShuffleProcessor.displayName = 'ContentShuffleProcessor'; diff --git a/invokeai/frontend/web/src/features/controlLayers/components/ControlAdapter/processors/DWOpenposeProcessor.tsx b/invokeai/frontend/web/src/features/controlLayers/components/ControlAdapter/processors/DWOpenposeProcessor.tsx deleted file mode 100644 index 4b197eb361..0000000000 --- a/invokeai/frontend/web/src/features/controlLayers/components/ControlAdapter/processors/DWOpenposeProcessor.tsx +++ /dev/null @@ -1,62 +0,0 @@ -import { Flex, FormControl, FormLabel, Switch } from '@invoke-ai/ui-library'; -import type { ProcessorComponentProps } from 'features/controlLayers/components/ControlAdapter/processors/types'; -import type { DWOpenposeProcessorConfig } from 'features/controlLayers/store/types'; -import { IMAGE_FILTERS } from 'features/controlLayers/store/types'; -import type { ChangeEvent } from 'react'; -import { memo, useCallback } from 'react'; -import { useTranslation } from 'react-i18next'; - -import ProcessorWrapper from './ProcessorWrapper'; - -type Props = ProcessorComponentProps; -const DEFAULTS = IMAGE_FILTERS['dw_openpose_image_processor'].buildDefaults(); - -export const DWOpenposeProcessor = memo(({ onChange, config }: Props) => { - const { t } = useTranslation(); - - const handleDrawBodyChanged = useCallback( - (e: ChangeEvent) => { - onChange({ ...config, draw_body: e.target.checked }); - }, - [config, onChange] - ); - - const handleDrawFaceChanged = useCallback( - (e: ChangeEvent) => { - onChange({ ...config, draw_face: e.target.checked }); - }, - [config, onChange] - ); - - const handleDrawHandsChanged = useCallback( - (e: ChangeEvent) => { - onChange({ ...config, draw_hands: e.target.checked }); - }, - [config, onChange] - ); - - return ( - - - - {t('controlnet.body')} - - - - {t('controlnet.face')} - - - - {t('controlnet.hands')} - - - - - ); -}); - -DWOpenposeProcessor.displayName = 'DWOpenposeProcessor'; diff --git a/invokeai/frontend/web/src/features/controlLayers/components/ControlAdapter/processors/DepthAnythingProcessor.tsx b/invokeai/frontend/web/src/features/controlLayers/components/ControlAdapter/processors/DepthAnythingProcessor.tsx deleted file mode 100644 index b4aa76ed6e..0000000000 --- a/invokeai/frontend/web/src/features/controlLayers/components/ControlAdapter/processors/DepthAnythingProcessor.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import type { ComboboxOnChange } from '@invoke-ai/ui-library'; -import { Combobox, FormControl, FormLabel } from '@invoke-ai/ui-library'; -import type { ProcessorComponentProps } from 'features/controlLayers/components/ControlAdapter/processors/types'; -import type { DepthAnythingModelSize, DepthAnythingProcessorConfig } from 'features/controlLayers/store/types'; -import { isDepthAnythingModelSize } from 'features/controlLayers/store/types'; -import { memo, useCallback, useMemo } from 'react'; -import { useTranslation } from 'react-i18next'; - -import ProcessorWrapper from './ProcessorWrapper'; - -type Props = ProcessorComponentProps; - -export const DepthAnythingProcessor = memo(({ onChange, config }: Props) => { - const { t } = useTranslation(); - const handleModelSizeChange = useCallback( - (v) => { - if (!isDepthAnythingModelSize(v?.value)) { - return; - } - onChange({ ...config, model_size: v.value }); - }, - [config, onChange] - ); - - const options: { label: string; value: DepthAnythingModelSize }[] = useMemo( - () => [ - { label: t('controlnet.depthAnythingSmallV2'), value: 'small_v2' }, - { label: t('controlnet.small'), value: 'small' }, - { label: t('controlnet.base'), value: 'base' }, - { label: t('controlnet.large'), value: 'large' }, - ], - [t] - ); - - const value = useMemo(() => options.filter((o) => o.value === config.model_size)[0], [options, config.model_size]); - - return ( - - - {t('controlnet.modelSize')} - - - - ); -}); - -DepthAnythingProcessor.displayName = 'DepthAnythingProcessor'; diff --git a/invokeai/frontend/web/src/features/controlLayers/components/ControlAdapter/processors/HedProcessor.tsx b/invokeai/frontend/web/src/features/controlLayers/components/ControlAdapter/processors/HedProcessor.tsx deleted file mode 100644 index 6c27e386c5..0000000000 --- a/invokeai/frontend/web/src/features/controlLayers/components/ControlAdapter/processors/HedProcessor.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import { FormControl, FormLabel, Switch } from '@invoke-ai/ui-library'; -import type { ProcessorComponentProps } from 'features/controlLayers/components/ControlAdapter/processors/types'; -import type { HedProcessorConfig } from 'features/controlLayers/store/types'; -import type { ChangeEvent } from 'react'; -import { memo, useCallback } from 'react'; -import { useTranslation } from 'react-i18next'; - -import ProcessorWrapper from './ProcessorWrapper'; - -type Props = ProcessorComponentProps; - -export const HedProcessor = memo(({ onChange, config }: Props) => { - const { t } = useTranslation(); - - const handleScribbleChanged = useCallback( - (e: ChangeEvent) => { - onChange({ ...config, scribble: e.target.checked }); - }, - [config, onChange] - ); - - return ( - - - {t('controlnet.scribble')} - - - - ); -}); - -HedProcessor.displayName = 'HedProcessor'; diff --git a/invokeai/frontend/web/src/features/controlLayers/components/ControlAdapter/processors/LineartProcessor.tsx b/invokeai/frontend/web/src/features/controlLayers/components/ControlAdapter/processors/LineartProcessor.tsx deleted file mode 100644 index 4abf7e920c..0000000000 --- a/invokeai/frontend/web/src/features/controlLayers/components/ControlAdapter/processors/LineartProcessor.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import { FormControl, FormLabel, Switch } from '@invoke-ai/ui-library'; -import type { ProcessorComponentProps } from 'features/controlLayers/components/ControlAdapter/processors/types'; -import type { LineartProcessorConfig } from 'features/controlLayers/store/types'; -import type { ChangeEvent } from 'react'; -import { memo, useCallback } from 'react'; -import { useTranslation } from 'react-i18next'; - -import ProcessorWrapper from './ProcessorWrapper'; - -type Props = ProcessorComponentProps; - -export const LineartProcessor = memo(({ onChange, config }: Props) => { - const { t } = useTranslation(); - - const handleCoarseChanged = useCallback( - (e: ChangeEvent) => { - onChange({ ...config, coarse: e.target.checked }); - }, - [config, onChange] - ); - - return ( - - - {t('controlnet.coarse')} - - - - ); -}); - -LineartProcessor.displayName = 'LineartProcessor'; diff --git a/invokeai/frontend/web/src/features/controlLayers/components/ControlAdapter/processors/MediapipeFaceProcessor.tsx b/invokeai/frontend/web/src/features/controlLayers/components/ControlAdapter/processors/MediapipeFaceProcessor.tsx deleted file mode 100644 index ad59e9f8d7..0000000000 --- a/invokeai/frontend/web/src/features/controlLayers/components/ControlAdapter/processors/MediapipeFaceProcessor.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library'; -import type { ProcessorComponentProps } from 'features/controlLayers/components/ControlAdapter/processors/types'; -import type { MediapipeFaceProcessorConfig } from 'features/controlLayers/store/types'; -import { IMAGE_FILTERS } from 'features/controlLayers/store/types'; -import { memo, useCallback } from 'react'; -import { useTranslation } from 'react-i18next'; - -import ProcessorWrapper from './ProcessorWrapper'; - -type Props = ProcessorComponentProps; -const DEFAULTS = IMAGE_FILTERS['mediapipe_face_processor'].buildDefaults(); - -export const MediapipeFaceProcessor = memo(({ onChange, config }: Props) => { - const { t } = useTranslation(); - - const handleMaxFacesChanged = useCallback( - (v: number) => { - onChange({ ...config, max_faces: v }); - }, - [config, onChange] - ); - - const handleMinConfidenceChanged = useCallback( - (v: number) => { - onChange({ ...config, min_confidence: v }); - }, - [config, onChange] - ); - - return ( - - - {t('controlnet.maxFaces')} - - - - - {t('controlnet.minConfidence')} - - - - - ); -}); - -MediapipeFaceProcessor.displayName = 'MediapipeFaceProcessor'; diff --git a/invokeai/frontend/web/src/features/controlLayers/components/ControlAdapter/processors/MidasDepthProcessor.tsx b/invokeai/frontend/web/src/features/controlLayers/components/ControlAdapter/processors/MidasDepthProcessor.tsx deleted file mode 100644 index c9740c23d3..0000000000 --- a/invokeai/frontend/web/src/features/controlLayers/components/ControlAdapter/processors/MidasDepthProcessor.tsx +++ /dev/null @@ -1,76 +0,0 @@ -import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library'; -import type { ProcessorComponentProps } from 'features/controlLayers/components/ControlAdapter/processors/types'; -import type { MidasDepthProcessorConfig } from 'features/controlLayers/store/types'; -import { IMAGE_FILTERS } from 'features/controlLayers/store/types'; -import { memo, useCallback } from 'react'; -import { useTranslation } from 'react-i18next'; - -import ProcessorWrapper from './ProcessorWrapper'; - -type Props = ProcessorComponentProps; -const DEFAULTS = IMAGE_FILTERS['midas_depth_image_processor'].buildDefaults(); - -export const MidasDepthProcessor = memo(({ onChange, config }: Props) => { - const { t } = useTranslation(); - - const handleAMultChanged = useCallback( - (v: number) => { - onChange({ ...config, a_mult: v }); - }, - [config, onChange] - ); - - const handleBgThChanged = useCallback( - (v: number) => { - onChange({ ...config, bg_th: v }); - }, - [config, onChange] - ); - - return ( - - - {t('controlnet.amult')} - - - - - {t('controlnet.bgth')} - - - - - ); -}); - -MidasDepthProcessor.displayName = 'MidasDepthProcessor'; diff --git a/invokeai/frontend/web/src/features/controlLayers/components/ControlAdapter/processors/MlsdImageProcessor.tsx b/invokeai/frontend/web/src/features/controlLayers/components/ControlAdapter/processors/MlsdImageProcessor.tsx deleted file mode 100644 index d907cbe705..0000000000 --- a/invokeai/frontend/web/src/features/controlLayers/components/ControlAdapter/processors/MlsdImageProcessor.tsx +++ /dev/null @@ -1,76 +0,0 @@ -import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library'; -import type { ProcessorComponentProps } from 'features/controlLayers/components/ControlAdapter/processors/types'; -import type { MlsdProcessorConfig } from 'features/controlLayers/store/types'; -import { IMAGE_FILTERS } from 'features/controlLayers/store/types'; -import { memo, useCallback } from 'react'; -import { useTranslation } from 'react-i18next'; - -import ProcessorWrapper from './ProcessorWrapper'; - -type Props = ProcessorComponentProps; -const DEFAULTS = IMAGE_FILTERS['mlsd_image_processor'].buildDefaults(); - -export const MlsdImageProcessor = memo(({ onChange, config }: Props) => { - const { t } = useTranslation(); - - const handleThrDChanged = useCallback( - (v: number) => { - onChange({ ...config, thr_d: v }); - }, - [config, onChange] - ); - - const handleThrVChanged = useCallback( - (v: number) => { - onChange({ ...config, thr_v: v }); - }, - [config, onChange] - ); - - return ( - - - {t('controlnet.w')} - - - - - {t('controlnet.h')} - - - - - ); -}); - -MlsdImageProcessor.displayName = 'MlsdImageProcessor'; diff --git a/invokeai/frontend/web/src/features/controlLayers/components/ControlAdapter/processors/PidiProcessor.tsx b/invokeai/frontend/web/src/features/controlLayers/components/ControlAdapter/processors/PidiProcessor.tsx deleted file mode 100644 index 6605baaadf..0000000000 --- a/invokeai/frontend/web/src/features/controlLayers/components/ControlAdapter/processors/PidiProcessor.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import { FormControl, FormLabel, Switch } from '@invoke-ai/ui-library'; -import type { ProcessorComponentProps } from 'features/controlLayers/components/ControlAdapter/processors/types'; -import type { PidiProcessorConfig } from 'features/controlLayers/store/types'; -import type { ChangeEvent } from 'react'; -import { useCallback } from 'react'; -import { useTranslation } from 'react-i18next'; - -import ProcessorWrapper from './ProcessorWrapper'; - -type Props = ProcessorComponentProps; - -export const PidiProcessor = ({ onChange, config }: Props) => { - const { t } = useTranslation(); - - const handleScribbleChanged = useCallback( - (e: ChangeEvent) => { - onChange({ ...config, scribble: e.target.checked }); - }, - [config, onChange] - ); - - const handleSafeChanged = useCallback( - (e: ChangeEvent) => { - onChange({ ...config, safe: e.target.checked }); - }, - [config, onChange] - ); - - return ( - - - {t('controlnet.scribble')} - - - - {t('controlnet.safe')} - - - - ); -}; - -PidiProcessor.displayName = 'PidiProcessor'; diff --git a/invokeai/frontend/web/src/features/controlLayers/components/ControlAdapter/processors/ProcessorWrapper.tsx b/invokeai/frontend/web/src/features/controlLayers/components/ControlAdapter/processors/ProcessorWrapper.tsx deleted file mode 100644 index 2b2468703b..0000000000 --- a/invokeai/frontend/web/src/features/controlLayers/components/ControlAdapter/processors/ProcessorWrapper.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { Flex } from '@invoke-ai/ui-library'; -import type { PropsWithChildren } from 'react'; -import { memo } from 'react'; - -type Props = PropsWithChildren; - -const ProcessorWrapper = (props: Props) => { - return ( - - {props.children} - - ); -}; - -export default memo(ProcessorWrapper); diff --git a/invokeai/frontend/web/src/features/controlLayers/components/ControlAdapter/processors/types.ts b/invokeai/frontend/web/src/features/controlLayers/components/ControlAdapter/processors/types.ts deleted file mode 100644 index a635e1f90f..0000000000 --- a/invokeai/frontend/web/src/features/controlLayers/components/ControlAdapter/processors/types.ts +++ /dev/null @@ -1,6 +0,0 @@ -import type { FilterConfig } from 'features/controlLayers/store/types'; - -export type ProcessorComponentProps = { - onChange: (config: T) => void; - config: T; -}; diff --git a/invokeai/frontend/web/src/features/controlLayers/components/ControlLayersSettingsPopover.tsx b/invokeai/frontend/web/src/features/controlLayers/components/ControlLayersSettingsPopover.tsx index afaafe69f2..0527377d6f 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/ControlLayersSettingsPopover.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/ControlLayersSettingsPopover.tsx @@ -1,4 +1,5 @@ import { + Button, Checkbox, Flex, FormControl, @@ -11,7 +12,11 @@ import { } from '@invoke-ai/ui-library'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { MaskOpacity } from 'features/controlLayers/components/MaskOpacity'; -import { clipToBboxChanged, invertScrollChanged } from 'features/controlLayers/store/canvasV2Slice'; +import { + clipToBboxChanged, + invertScrollChanged, + rasterizationCachesInvalidated, +} from 'features/controlLayers/store/canvasV2Slice'; import type { ChangeEvent } from 'react'; import { memo, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; @@ -30,6 +35,9 @@ const ControlLayersSettingsPopover = () => { (e: ChangeEvent) => dispatch(clipToBboxChanged(e.target.checked)), [dispatch] ); + const invalidateRasterizationCaches = useCallback(() => { + dispatch(rasterizationCachesInvalidated()); + }, [dispatch]); return ( @@ -47,6 +55,9 @@ const ControlLayersSettingsPopover = () => { {t('unifiedCanvas.clipToBbox')} + diff --git a/invokeai/frontend/web/src/features/controlLayers/components/DeleteAllLayersButton.tsx b/invokeai/frontend/web/src/features/controlLayers/components/DeleteAllLayersButton.tsx index 16a3c88d83..765608e6d7 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/DeleteAllLayersButton.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/DeleteAllLayersButton.tsx @@ -11,7 +11,7 @@ export const DeleteAllLayersButton = memo(() => { const entityCount = useAppSelector((s) => { return ( s.canvasV2.regions.entities.length + - s.canvasV2.controlAdapters.entities.length + + // s.canvasV2.controlAdapters.entities.length + s.canvasV2.ipAdapters.entities.length + s.canvasV2.layers.entities.length ); diff --git a/invokeai/frontend/web/src/features/controlLayers/components/Layer/Layer.tsx b/invokeai/frontend/web/src/features/controlLayers/components/Layer/Layer.tsx index 65c5501039..a91f104420 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/Layer/Layer.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/Layer/Layer.tsx @@ -10,8 +10,6 @@ import { EntityIdentifierContext } from 'features/controlLayers/contexts/EntityI import type { CanvasEntityIdentifier } from 'features/controlLayers/store/types'; import { memo, useMemo } from 'react'; -import { LayerOpacity } from './LayerOpacity'; - type Props = { id: string; }; @@ -26,7 +24,6 @@ export const Layer = memo(({ id }: Props) => { - diff --git a/invokeai/frontend/web/src/features/controlLayers/components/Layer/LayerOpacity.tsx b/invokeai/frontend/web/src/features/controlLayers/components/Layer/LayerOpacity.tsx deleted file mode 100644 index 0d42659966..0000000000 --- a/invokeai/frontend/web/src/features/controlLayers/components/Layer/LayerOpacity.tsx +++ /dev/null @@ -1,82 +0,0 @@ -import { - CompositeNumberInput, - CompositeSlider, - Flex, - FormControl, - FormLabel, - IconButton, - Popover, - PopoverArrow, - PopoverBody, - PopoverContent, - PopoverTrigger, -} from '@invoke-ai/ui-library'; -import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import { stopPropagation } from 'common/util/stopPropagation'; -import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext'; -import { layerOpacityChanged } from 'features/controlLayers/store/canvasV2Slice'; -import { selectLayerOrThrow } from 'features/controlLayers/store/layersReducers'; -import { memo, useCallback } from 'react'; -import { useTranslation } from 'react-i18next'; -import { PiDropHalfFill } from 'react-icons/pi'; - -const marks = [0, 25, 50, 75, 100]; -const formatPct = (v: number | string) => `${v} %`; - -export const LayerOpacity = memo(() => { - const entityIdentifier = useEntityIdentifierContext(); - const { t } = useTranslation(); - const dispatch = useAppDispatch(); - const opacity = useAppSelector((s) => Math.round(selectLayerOrThrow(s.canvasV2, entityIdentifier.id).opacity * 100)); - const onChangeOpacity = useCallback( - (v: number) => { - dispatch(layerOpacityChanged({ id: entityIdentifier.id, opacity: v / 100 })); - }, - [dispatch, entityIdentifier.id] - ); - return ( - - - } - variant="ghost" - onDoubleClick={stopPropagation} - /> - - - - - - - {t('controlLayers.opacity')} - - - - - - - - ); -}); - -LayerOpacity.displayName = 'LayerOpacity'; diff --git a/invokeai/frontend/web/src/features/controlLayers/components/common/CanvasEntityActionMenuItems.tsx b/invokeai/frontend/web/src/features/controlLayers/components/common/CanvasEntityActionMenuItems.tsx index b75e87d828..0a812ce0c2 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/common/CanvasEntityActionMenuItems.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/common/CanvasEntityActionMenuItems.tsx @@ -40,11 +40,6 @@ const getIndexAndCount = ( index: canvasV2.layers.entities.findIndex((entity) => entity.id === id), count: canvasV2.layers.entities.length, }; - } else if (type === 'control_adapter') { - return { - index: canvasV2.controlAdapters.entities.findIndex((entity) => entity.id === id), - count: canvasV2.controlAdapters.entities.length, - }; } else if (type === 'regional_guidance') { return { index: canvasV2.regions.entities.findIndex((entity) => entity.id === id), diff --git a/invokeai/frontend/web/src/features/controlLayers/hooks/addLayerHooks.ts b/invokeai/frontend/web/src/features/controlLayers/hooks/addLayerHooks.ts index c41a95384c..c7fd177d75 100644 --- a/invokeai/frontend/web/src/features/controlLayers/hooks/addLayerHooks.ts +++ b/invokeai/frontend/web/src/features/controlLayers/hooks/addLayerHooks.ts @@ -1,6 +1,6 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { deepClone } from 'common/util/deepClone'; -import { caAdded, ipaAdded, rgIPAdapterAdded } from 'features/controlLayers/store/canvasV2Slice'; +import { ipaAdded, rgIPAdapterAdded } from 'features/controlLayers/store/canvasV2Slice'; import { IMAGE_FILTERS, initialControlNetV2, @@ -37,7 +37,7 @@ export const useAddCALayer = () => { const initialConfig = deepClone(model.type === 'controlnet' ? initialControlNetV2 : initialT2IAdapterV2); const config = { ...initialConfig, model: zModelIdentifierField.parse(model), processorConfig }; - dispatch(caAdded({ config })); + // dispatch(caAdded({ config })); }, [dispatch, model, baseModel]); return [addCALayer, isDisabled] as const; diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasFilter.ts b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasFilter.ts index dd1581612f..f718b78a61 100644 --- a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasFilter.ts +++ b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasFilter.ts @@ -100,7 +100,12 @@ export class CanvasFilter { this.manager.stateApi.rasterizeEntity({ entityIdentifier: this.parent.getEntityIdentifier(), imageObject: this.imageState, - position: { x: Math.round(rect.x), y: Math.round(rect.y) }, + rect: { + x: Math.round(rect.x), + y: Math.round(rect.y), + width: this.imageState.image.height, + height: this.imageState.image.width, + }, }); this.parent.renderer.showObjects(); this.manager.stateApi.$filteringEntity.set(null); diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasManager.ts b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasManager.ts index 6777bbd3ff..9d1f76ff4d 100644 --- a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasManager.ts +++ b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasManager.ts @@ -9,20 +9,27 @@ import { konvaNodeToBlob, konvaNodeToImageData, nanoid, + previewBlob, } from 'features/controlLayers/konva/util'; import type { Extents, ExtentsResult, GetBboxTask, WorkerLogMessage } from 'features/controlLayers/konva/worker'; -import type { CanvasV2State, Coordinate, Dimensions, GenerationMode, Rect } from 'features/controlLayers/store/types'; +import type { + CanvasV2State, + Coordinate, + Dimensions, + GenerationMode, + ImageCache, + Rect, +} from 'features/controlLayers/store/types'; import { isValidLayerWithoutControlAdapter } from 'features/nodes/util/graph/generation/addLayers'; import type Konva from 'konva'; -import { clamp } from 'lodash-es'; +import { clamp, isEqual } from 'lodash-es'; import { atom } from 'nanostores'; import type { Logger } from 'roarr'; import { getImageDTO, uploadImage } from 'services/api/endpoints/images'; import type { ImageDTO } from 'services/api/types'; -import { assert } from 'tsafe'; import { CanvasBackground } from './CanvasBackground'; -import { CanvasControlAdapter } from './CanvasControlAdapter'; +import type { CanvasControlAdapter } from './CanvasControlAdapter'; import { CanvasLayerAdapter } from './CanvasLayerAdapter'; import { CanvasMaskAdapter } from './CanvasMaskAdapter'; import { CanvasPreview } from './CanvasPreview'; @@ -144,40 +151,15 @@ export class CanvasManager { this._worker.postMessage(task, [data.buffer]); } - async renderControlAdapters() { - const { entities } = this.stateApi.getControlAdaptersState(); - - for (const canvasControlAdapter of this.controlAdapters.values()) { - if (!entities.find((ca) => ca.id === canvasControlAdapter.id)) { - canvasControlAdapter.destroy(); - this.controlAdapters.delete(canvasControlAdapter.id); - } - } - - for (const entity of entities) { - let adapter = this.controlAdapters.get(entity.id); - if (!adapter) { - adapter = new CanvasControlAdapter(entity, this); - this.controlAdapters.set(adapter.id, adapter); - this.stage.add(adapter.konva.layer); - } - await adapter.render(entity); - } - } - arrangeEntities() { - const { getLayersState, getControlAdaptersState, getRegionsState } = this.stateApi; + const { getLayersState, getRegionsState } = this.stateApi; const layers = getLayersState().entities; - const controlAdapters = getControlAdaptersState().entities; const regions = getRegionsState().entities; let zIndex = 0; this.background.konva.layer.zIndex(++zIndex); for (const layer of layers) { this.layers.get(layer.id)?.konva.layer.zIndex(++zIndex); } - for (const ca of controlAdapters) { - this.controlAdapters.get(ca.id)?.konva.layer.zIndex(++zIndex); - } for (const rg of regions) { this.regions.get(rg.id)?.konva.layer.zIndex(++zIndex); } @@ -358,16 +340,6 @@ export class CanvasManager { }); } - if ( - this._isFirstRender || - state.controlAdapters.entities !== this._prevState.controlAdapters.entities || - state.tool.selected !== this._prevState.tool.selected || - state.selectedEntityIdentifier?.id !== this._prevState.selectedEntityIdentifier?.id - ) { - this.log.debug('Rendering control adapters'); - await this.renderControlAdapters(); - } - this.stateApi.$toolState.set(state.tool); this.stateApi.$selectedEntityIdentifier.set(state.selectedEntityIdentifier); this.stateApi.$selectedEntity.set(this.stateApi.getSelectedEntity()); @@ -382,12 +354,7 @@ export class CanvasManager { await this.preview.bbox.render(); } - if ( - this._isFirstRender || - state.layers !== this._prevState.layers || - state.controlAdapters !== this._prevState.controlAdapters || - state.regions !== this._prevState.regions - ) { + if (this._isFirstRender || state.layers !== this._prevState.layers || state.regions !== this._prevState.regions) { // this.log.debug('Updating entity bboxes'); // debouncedUpdateBboxes(stage, canvasV2.layers, canvasV2.controlAdapters, canvasV2.regions, onBboxChanged); } @@ -400,7 +367,6 @@ export class CanvasManager { if ( this._isFirstRender || state.layers.entities !== this._prevState.layers.entities || - state.controlAdapters.entities !== this._prevState.controlAdapters.entities || state.regions.entities !== this._prevState.regions.entities || state.inpaintMask !== this._prevState.inpaintMask || state.selectedEntityIdentifier?.id !== this._prevState.selectedEntityIdentifier?.id @@ -569,45 +535,43 @@ export class CanvasManager { return konvaNodeToImageData(this.getCompositeLayerStageClone(), rect); }; - getCompositeLayerImageDTO = async (rect?: Rect): Promise => { + getCompositeRasterizedImageCache = (rect: Rect): ImageCache | null => { + const layerState = this.stateApi.getLayersState(); + const imageCache = layerState.compositeRasterizationCache.find((cache) => isEqual(cache.rect, rect)); + return imageCache ?? null; + }; + + getCompositeLayerImageDTO = async (rect: Rect): Promise => { + let imageDTO: ImageDTO | null = null; + const compositeRasterizedImageCache = this.getCompositeRasterizedImageCache(rect); + + if (compositeRasterizedImageCache) { + imageDTO = await getImageDTO(compositeRasterizedImageCache.imageName); + if (imageDTO) { + this.log.trace({ rect, compositeRasterizedImageCache, imageDTO }, 'Using cached composite rasterized image'); + return imageDTO; + } + } + + this.log.trace({ rect }, 'Rasterizing composite layer'); + const blob = await this.getCompositeLayerBlob(rect); - const imageDTO = await uploadImage(blob, 'composite-layer.png', 'general', true); - this.stateApi.setLayerImageCache(imageDTO); + + if (this._isDebugging) { + previewBlob(blob, 'Rasterized entity'); + } + + imageDTO = await uploadImage(blob, 'composite-layer.png', 'general', true); + this.stateApi.compositeLayerRasterized({ imageName: imageDTO.image_name, rect }); return imageDTO; }; getInpaintMaskBlob = (rect?: Rect): Promise => { - return this.inpaintMask.renderer.getBlob({ rect }); + return this.inpaintMask.renderer.getBlob(rect); }; getInpaintMaskImageData = (rect?: Rect): ImageData => { - return this.inpaintMask.renderer.getImageData({ rect }); - }; - - getInpaintMaskImageDTO = async (rect?: Rect): Promise => { - const blob = await this.inpaintMask.renderer.getBlob({ rect }); - const imageDTO = await uploadImage(blob, 'inpaint-mask.png', 'mask', true); - this.stateApi.setInpaintMaskImageCache(imageDTO); - return imageDTO; - }; - - getRegionMaskImageDTO = async (id: string, rect?: Rect): Promise => { - const region = this.stateApi.getEntity({ id, type: 'regional_guidance' }); - assert(region?.type === 'regional_guidance'); - if (region.state.imageCache) { - const imageDTO = await getImageDTO(region.state.imageCache); - if (imageDTO) { - return imageDTO; - } - } - return region.adapter.renderer.getImageDTO({ - rect, - category: 'other', - is_intermediate: true, - onUploaded: (imageDTO) => { - this.stateApi.setRegionMaskImageCache(region.state.id, imageDTO); - }, - }); + return this.inpaintMask.renderer.getImageData(rect); }; getGenerationMode(): GenerationMode { diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasObjectRenderer.ts b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasObjectRenderer.ts index 4f6b16cda3..726ec21cf5 100644 --- a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasObjectRenderer.ts +++ b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasObjectRenderer.ts @@ -14,14 +14,16 @@ import type { CanvasEraserLineState, CanvasImageState, CanvasRectState, + ImageCache, Rect, RgbColor, } from 'features/controlLayers/store/types'; import { imageDTOToImageObject } from 'features/controlLayers/store/types'; import Konva from 'konva'; +import { isEqual } from 'lodash-es'; import type { Logger } from 'roarr'; import { getImageDTO, uploadImage } from 'services/api/endpoints/images'; -import type { ImageCategory, ImageDTO } from 'services/api/types'; +import type { ImageDTO } from 'services/api/types'; import { assert } from 'tsafe'; /** @@ -62,12 +64,6 @@ export class CanvasObjectRenderer { */ renderers: Map = new Map(); - /** - * A cache of the rasterized image data URL. If the cache is null, the parent has not been rasterized since its last - * change. - */ - rasterizedImageCache: string | null = null; - /** * A object containing singleton Konva nodes. */ @@ -168,19 +164,6 @@ export class CanvasObjectRenderer { didRender = (await this.renderObject(this.buffer)) || didRender; } - if (didRender && this.rasterizedImageCache) { - const hasOneObject = this.renderers.size === 1; - const firstObject = Array.from(this.renderers.values())[0]; - if ( - hasOneObject && - firstObject && - firstObject.state.type === 'image' && - firstObject.state.image.image_name !== this.rasterizedImageCache - ) { - this.rasterizedImageCache = null; - } - } - return didRender; }; @@ -376,29 +359,37 @@ export class CanvasObjectRenderer { return this.renderers.size > 0 || this.buffer !== null; }; + getRasterizedImageCache = (rect: Rect): ImageCache | null => { + const imageCache = this.parent.state.rasterizationCache.find((cache) => isEqual(cache.rect, rect)); + return imageCache ?? null; + }; + /** - * Rasterizes the parent entity. If the entity has a rasterization cache, the cached image is returned after - * validating that it exists on the server. + * Rasterizes the parent entity. If the entity has a rasterization cache for the given rect, the cached image is + * returned. Otherwise, the entity is rasterized and the image is uploaded to the server. * - * The rasterization cache is reset when the entity's objects change. The buffer object is not considered part of the - * entity's objects for this purpose. + * The rasterization cache is reset when the entity's state changes. The buffer object is not considered part of the + * entity state for this purpose as it is a temporary object. * + * @param rect The rect to rasterize. If omitted, the entity's full rect will be used. * @returns A promise that resolves to the rasterized image DTO. */ - rasterize = async (): Promise => { - this.log.debug('Rasterizing entity'); - + rasterize = async (rect?: Rect): Promise => { + rect = rect ?? this.parent.transformer.getRelativeRect(); let imageDTO: ImageDTO | null = null; - if (this.rasterizedImageCache) { - imageDTO = await getImageDTO(this.rasterizedImageCache); + const rasterizedImageCache = this.getRasterizedImageCache(rect); + + if (rasterizedImageCache) { + imageDTO = await getImageDTO(rasterizedImageCache.imageName); + if (imageDTO) { + this.log.trace({ rect, rasterizedImageCache, imageDTO }, 'Using cached rasterized image'); + return imageDTO; + } } - if (imageDTO) { - return imageDTO; - } + this.log.trace({ rect }, 'Rasterizing entity'); - const rect = this.parent.transformer.getRelativeRect(); - const blob = await this.getBlob({ rect }); + const blob = await this.getBlob(rect); if (this.manager._isDebugging) { previewBlob(blob, 'Rasterized entity'); } @@ -408,41 +399,20 @@ export class CanvasObjectRenderer { this.manager.stateApi.rasterizeEntity({ entityIdentifier: this.parent.getEntityIdentifier(), imageObject, - position: { x: Math.round(rect.x), y: Math.round(rect.y) }, + rect: { x: Math.round(rect.x), y: Math.round(rect.y), width: imageDTO.width, height: imageDTO.height }, }); - this.rasterizedImageCache = imageDTO.image_name; - return imageDTO; }; - getBlob = ({ rect }: { rect?: Rect }): Promise => { + getBlob = (rect?: Rect): Promise => { return konvaNodeToBlob(this.konva.objectGroup.clone(), rect); }; - getImageData = ({ rect }: { rect?: Rect }): ImageData => { + getImageData = (rect?: Rect): ImageData => { return konvaNodeToImageData(this.konva.objectGroup.clone(), rect); }; - getImageDTO = async ({ - rect, - category, - is_intermediate, - onUploaded, - }: { - rect?: Rect; - category: ImageCategory; - is_intermediate: boolean; - onUploaded?: (imageDTO: ImageDTO) => void; - }): Promise => { - const blob = await this.getBlob({ rect }); - const imageDTO = await uploadImage(blob, `${this.id}.png`, category, is_intermediate); - if (onUploaded) { - onUploaded(imageDTO); - } - return imageDTO; - }; - /** * Destroys this renderer and all of its object renderers. */ diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasStateApi.ts b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasStateApi.ts index c880f60f59..4e20aebbe1 100644 --- a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasStateApi.ts +++ b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasStateApi.ts @@ -26,9 +26,7 @@ import { entityReset, entitySelected, eraserWidthChanged, - imImageCacheChanged, - layerImageCacheChanged, - rgImageCacheChanged, + layerCompositeRasterized, toolBufferChanged, toolChanged, } from 'features/controlLayers/store/canvasV2Slice'; @@ -51,7 +49,6 @@ import type { import { RGBA_RED } from 'features/controlLayers/store/types'; import type { WritableAtom } from 'nanostores'; import { atom } from 'nanostores'; -import type { ImageDTO } from 'services/api/types'; type EntityStateAndAdapter = | { @@ -118,6 +115,10 @@ export class CanvasStateApi { log.trace(arg, 'Rasterizing entity'); this._store.dispatch(entityRasterized(arg)); }; + compositeLayerRasterized = (arg: { imageName: string; rect: Rect }) => { + log.trace(arg, 'Composite layer rasterized'); + this._store.dispatch(layerCompositeRasterized(arg)); + }; setSelectedEntity = (arg: EntityIdentifierPayload) => { log.trace({ arg }, 'Setting selected entity'); this._store.dispatch(entitySelected(arg)); @@ -134,18 +135,6 @@ export class CanvasStateApi { log.trace({ width }, 'Setting eraser width'); this._store.dispatch(eraserWidthChanged(width)); }; - setRegionMaskImageCache = (id: string, imageDTO: ImageDTO) => { - log.trace({ id, imageDTO }, 'Setting region mask image cache'); - this._store.dispatch(rgImageCacheChanged({ id, imageDTO })); - }; - setInpaintMaskImageCache = (imageDTO: ImageDTO) => { - log.trace({ imageDTO }, 'Setting inpaint mask image cache'); - this._store.dispatch(imImageCacheChanged({ imageDTO })); - }; - setLayerImageCache = (imageDTO: ImageDTO) => { - log.trace({ imageDTO }, 'Setting layer image cache'); - this._store.dispatch(layerImageCacheChanged({ imageDTO })); - }; setTool = (tool: Tool) => { log.trace({ tool }, 'Setting tool'); this._store.dispatch(toolChanged(tool)); @@ -171,9 +160,6 @@ export class CanvasStateApi { getLayersState = () => { return this.getState().layers; }; - getControlAdaptersState = () => { - return this.getState().controlAdapters; - }; getInpaintMaskState = () => { return this.getState().inpaintMask; }; @@ -202,9 +188,6 @@ export class CanvasStateApi { if (identifier.type === 'layer') { entityState = state.layers.entities.find((i) => i.id === identifier.id) ?? null; entityAdapter = this.manager.layers.get(identifier.id) ?? null; - } else if (identifier.type === 'control_adapter') { - entityState = state.controlAdapters.entities.find((i) => i.id === identifier.id) ?? null; - entityAdapter = this.manager.controlAdapters.get(identifier.id) ?? null; } else if (identifier.type === 'regional_guidance') { entityState = state.regions.entities.find((i) => i.id === identifier.id) ?? null; entityAdapter = this.manager.regions.get(identifier.id) ?? null; diff --git a/invokeai/frontend/web/src/features/controlLayers/store/canvasV2Slice.ts b/invokeai/frontend/web/src/features/controlLayers/store/canvasV2Slice.ts index 0470b36281..1c69a67a97 100644 --- a/invokeai/frontend/web/src/features/controlLayers/store/canvasV2Slice.ts +++ b/invokeai/frontend/web/src/features/controlLayers/store/canvasV2Slice.ts @@ -5,7 +5,6 @@ import { moveOneToEnd, moveOneToStart, moveToEnd, moveToStart } from 'common/uti import { deepClone } from 'common/util/deepClone'; import { bboxReducers } from 'features/controlLayers/store/bboxReducers'; import { compositingReducers } from 'features/controlLayers/store/compositingReducers'; -import { controlAdaptersReducers } from 'features/controlLayers/store/controlAdaptersReducers'; import { inpaintMaskReducers } from 'features/controlLayers/store/inpaintMaskReducers'; import { ipAdaptersReducers } from 'features/controlLayers/store/ipAdaptersReducers'; import { layersReducers } from 'features/controlLayers/store/layersReducers'; @@ -18,13 +17,16 @@ import { toolReducers } from 'features/controlLayers/store/toolReducers'; import { getScaledBoundingBoxDimensions } from 'features/controlLayers/util/getScaledBoundingBoxDimensions'; import { initialAspectRatioState } from 'features/parameters/components/DocumentSize/constants'; import { getOptimalDimension } from 'features/parameters/util/optimalDimension'; -import { pick } from 'lodash-es'; +import { isEqual, pick } from 'lodash-es'; import { atom } from 'nanostores'; import type { InvocationDenoiseProgressEvent } from 'services/events/types'; import { assert } from 'tsafe'; import type { CanvasEntityIdentifier, + CanvasInpaintMaskState, + CanvasLayerState, + CanvasRegionalGuidanceState, CanvasV2State, Coordinate, EntityBrushLineAddedPayload, @@ -41,8 +43,7 @@ import { IMAGE_FILTERS, RGBA_RED } from './types'; const initialState: CanvasV2State = { _version: 3, selectedEntityIdentifier: null, - layers: { entities: [], imageCache: null }, - controlAdapters: { entities: [] }, + layers: { entities: [], compositeRasterizationCache: [] }, ipAdapters: { entities: [] }, regions: { entities: [] }, loras: [], @@ -50,7 +51,7 @@ const initialState: CanvasV2State = { id: 'inpaint_mask', type: 'inpaint_mask', fill: RGBA_RED, - imageCache: null, + rasterizationCache: [], isEnabled: true, objects: [], position: { @@ -144,8 +145,6 @@ export function selectEntity(state: CanvasV2State, { id, type }: CanvasEntityIde switch (type) { case 'layer': return state.layers.entities.find((layer) => layer.id === id); - case 'control_adapter': - return state.controlAdapters.entities.find((ca) => ca.id === id); case 'inpaint_mask': return state.inpaintMask; case 'regional_guidance': @@ -157,13 +156,37 @@ export function selectEntity(state: CanvasV2State, { id, type }: CanvasEntityIde } } +const invalidateCompositeRasterizationCache = (entity: CanvasLayerState, state: CanvasV2State) => { + if (entity.controlAdapter === null) { + state.layers.compositeRasterizationCache = []; + } +}; + +const invalidateRasterizationCaches = ( + entity: CanvasLayerState | CanvasInpaintMaskState | CanvasRegionalGuidanceState, + state: CanvasV2State +) => { + // TODO(psyche): We can be more efficient and only invalidate caches when the entity's changes intersect with the + // cached rect. + + // Reset the entity's rasterization cache + entity.rasterizationCache = []; + + // When an individual layer has its cache reset, we must also reset the composite rasterization cache because the + // layer's image data will contribute to the composite layer's image data. + // If the layer is used as a control layer, it will not contribute to the composite layer, so we do not need to reset + // its cache. + if (entity.type === 'layer') { + invalidateCompositeRasterizationCache(entity, state); + } +}; + export const canvasV2Slice = createSlice({ name: 'canvasV2', initialState, reducers: { ...layersReducers, ...ipAdaptersReducers, - ...controlAdaptersReducers, ...regionsReducers, ...lorasReducers, ...paramsReducers, @@ -182,16 +205,11 @@ export const canvasV2Slice = createSlice({ const entity = selectEntity(state, entityIdentifier); if (!entity) { return; - } else if (entity.type === 'layer') { + } else if (entity.type === 'layer' || entity.type === 'inpaint_mask' || entity.type === 'regional_guidance') { entity.isEnabled = true; entity.objects = []; entity.position = { x: 0, y: 0 }; - state.layers.imageCache = null; - } else if (entity.type === 'inpaint_mask' || entity.type === 'regional_guidance') { - entity.isEnabled = true; - entity.objects = []; - entity.position = { x: 0, y: 0 }; - entity.imageCache = null; + invalidateRasterizationCaches(entity, state); } else { assert(false, 'Not implemented'); } @@ -209,32 +227,28 @@ export const canvasV2Slice = createSlice({ const entity = selectEntity(state, entityIdentifier); if (!entity) { return; - } else if (entity.type === 'layer') { + } + + if (entity.type === 'layer' || entity.type === 'inpaint_mask' || entity.type === 'regional_guidance') { entity.position = position; - state.layers.imageCache = null; - } else if (entity.type === 'inpaint_mask' || entity.type === 'regional_guidance') { - entity.position = position; - entity.imageCache = null; - } else { - assert(false, 'Not implemented'); + // When an entity is moved, we need to invalidate the rasterization caches. + invalidateRasterizationCaches(entity, state); } }, entityRasterized: (state, action: PayloadAction) => { - const { entityIdentifier, imageObject, position } = action.payload; + const { entityIdentifier, imageObject, rect } = action.payload; const entity = selectEntity(state, entityIdentifier); if (!entity) { return; - } else if (entity.type === 'layer') { + } + + if (entity.type === 'layer' || entity.type === 'inpaint_mask' || entity.type === 'regional_guidance') { entity.objects = [imageObject]; - entity.position = position; - entity.imageCache = imageObject.image.image_name; - state.layers.imageCache = null; - } else if (entity.type === 'inpaint_mask' || entity.type === 'regional_guidance') { - entity.objects = [imageObject]; - entity.position = position; - entity.imageCache = imageObject.image.image_name; - } else { - assert(false, 'Not implemented'); + entity.position = { x: rect.x, y: rect.y }; + // Remove the cache for the given rect. This should never happen, because we should never rasterize the same + // rect twice. Just in case, we remove the old cache. + entity.rasterizationCache = entity.rasterizationCache.filter((cache) => !isEqual(cache.rect, rect)); + entity.rasterizationCache.push({ imageName: imageObject.image.image_name, rect }); } }, entityBrushLineAdded: (state, action: PayloadAction) => { @@ -242,14 +256,12 @@ export const canvasV2Slice = createSlice({ const entity = selectEntity(state, entityIdentifier); if (!entity) { return; - } else if (entity.type === 'layer') { + } + + if (entity.type === 'layer' || entity.type === 'inpaint_mask' || entity.type === 'regional_guidance') { entity.objects.push(brushLine); - state.layers.imageCache = null; - } else if (entity.type === 'inpaint_mask' || entity.type === 'regional_guidance') { - entity.objects.push(brushLine); - entity.imageCache = null; - } else { - assert(false, 'Not implemented'); + // When adding a brush line, we need to invalidate the rasterization caches. + invalidateRasterizationCaches(entity, state); } }, entityEraserLineAdded: (state, action: PayloadAction) => { @@ -257,12 +269,10 @@ export const canvasV2Slice = createSlice({ const entity = selectEntity(state, entityIdentifier); if (!entity) { return; - } else if (entity.type === 'layer') { + } else if (entity.type === 'layer' || entity.type === 'inpaint_mask' || entity.type === 'regional_guidance') { entity.objects.push(eraserLine); - state.layers.imageCache = null; - } else if (entity.type === 'inpaint_mask' || entity.type === 'regional_guidance') { - entity.objects.push(eraserLine); - entity.imageCache = null; + // When adding an eraser line, we need to invalidate the rasterization caches. + invalidateRasterizationCaches(entity, state); } else { assert(false, 'Not implemented'); } @@ -274,19 +284,21 @@ export const canvasV2Slice = createSlice({ return; } else if (entity.type === 'layer') { entity.objects.push(rect); - state.layers.imageCache = null; - } else if (entity.type === 'inpaint_mask' || entity.type === 'regional_guidance') { - entity.objects.push(rect); - entity.imageCache = null; + // When adding an eraser line, we need to invalidate the rasterization caches. + invalidateRasterizationCaches(entity, state); } else { assert(false, 'Not implemented'); } }, entityDeleted: (state, action: PayloadAction) => { const { entityIdentifier } = action.payload; + const entity = selectEntity(state, entityIdentifier); + if (entity?.type === 'layer') { + // When a layer is deleted, we may need to invalidate the composite rasterization cache. + invalidateCompositeRasterizationCache(entity, state); + } if (entityIdentifier.type === 'layer') { state.layers.entities = state.layers.entities.filter((layer) => layer.id !== entityIdentifier.id); - state.layers.imageCache = null; } else if (entityIdentifier.type === 'regional_guidance') { state.regions.entities = state.regions.entities.filter((rg) => rg.id !== entityIdentifier.id); } else { @@ -301,11 +313,10 @@ export const canvasV2Slice = createSlice({ } if (entity.type === 'layer') { moveOneToEnd(state.layers.entities, entity); - state.layers.imageCache = null; + // When arranging an entity, we may need to invalidate the composite rasterization cache. + invalidateCompositeRasterizationCache(entity, state); } else if (entity.type === 'regional_guidance') { moveOneToEnd(state.regions.entities, entity); - } else if (entity.type === 'control_adapter') { - moveOneToEnd(state.controlAdapters.entities, entity); } }, entityArrangedToFront: (state, action: PayloadAction) => { @@ -316,11 +327,10 @@ export const canvasV2Slice = createSlice({ } if (entity.type === 'layer') { moveToEnd(state.layers.entities, entity); - state.layers.imageCache = null; + // When arranging an entity, we may need to invalidate the composite rasterization cache. + invalidateCompositeRasterizationCache(entity, state); } else if (entity.type === 'regional_guidance') { moveToEnd(state.regions.entities, entity); - } else if (entity.type === 'control_adapter') { - moveToEnd(state.controlAdapters.entities, entity); } }, entityArrangedBackwardOne: (state, action: PayloadAction) => { @@ -331,11 +341,10 @@ export const canvasV2Slice = createSlice({ } if (entity.type === 'layer') { moveOneToStart(state.layers.entities, entity); - state.layers.imageCache = null; + // When arranging an entity, we may need to invalidate the composite rasterization cache. + invalidateCompositeRasterizationCache(entity, state); } else if (entity.type === 'regional_guidance') { moveOneToStart(state.regions.entities, entity); - } else if (entity.type === 'control_adapter') { - moveOneToStart(state.controlAdapters.entities, entity); } }, entityArrangedToBack: (state, action: PayloadAction) => { @@ -346,19 +355,17 @@ export const canvasV2Slice = createSlice({ } if (entity.type === 'layer') { moveToStart(state.layers.entities, entity); - state.layers.imageCache = null; + // When arranging an entity, we may need to invalidate the composite rasterization cache. + invalidateCompositeRasterizationCache(entity, state); } else if (entity.type === 'regional_guidance') { moveToStart(state.regions.entities, entity); - } else if (entity.type === 'control_adapter') { - moveToStart(state.controlAdapters.entities, entity); } }, allEntitiesDeleted: (state) => { state.regions.entities = []; state.layers.entities = []; - state.layers.imageCache = null; + state.layers.compositeRasterizationCache = []; state.ipAdapters.entities = []; - state.controlAdapters.entities = []; }, filterSelected: (state, action: PayloadAction<{ type: FilterConfig['type'] }>) => { state.filter.config = IMAGE_FILTERS[action.payload.type].buildDefaults(); @@ -366,6 +373,23 @@ export const canvasV2Slice = createSlice({ filterConfigChanged: (state, action: PayloadAction<{ config: FilterConfig }>) => { state.filter.config = action.payload.config; }, + rasterizationCachesInvalidated: (state) => { + // Invalidate the rasterization caches for all entities. + + // Layers & composite layer + state.layers.compositeRasterizationCache = []; + for (const layer of state.layers.entities) { + layer.rasterizationCache = []; + } + + // Regions + for (const region of state.regions.entities) { + region.rasterizationCache = []; + } + + // Inpaint mask + state.inpaintMask.rasterizationCache = []; + }, canvasReset: (state) => { state.bbox = deepClone(initialState.bbox); const optimalDimension = getOptimalDimension(state.params.model); @@ -374,7 +398,6 @@ export const canvasV2Slice = createSlice({ const size = pick(state.bbox.rect, 'width', 'height'); state.bbox.scaledSize = getScaledBoundingBoxDimensions(size, optimalDimension); - state.controlAdapters = deepClone(initialState.controlAdapters); state.ipAdapters = deepClone(initialState.ipAdapters); state.layers = deepClone(initialState.layers); state.regions = deepClone(initialState.regions); @@ -397,6 +420,7 @@ export const { allEntitiesDeleted, clipToBboxChanged, canvasReset, + rasterizationCachesInvalidated, // All entities entitySelected, entityReset, @@ -424,14 +448,13 @@ export const { // layers layerAdded, layerRecalled, - layerOpacityChanged, layerAllDeleted, - layerImageCacheChanged, layerUsedAsControlChanged, layerControlAdapterModelChanged, layerControlAdapterControlModeChanged, layerControlAdapterWeightChanged, layerControlAdapterBeginEndStepPctChanged, + layerCompositeRasterized, // IP Adapters ipaAdded, ipaRecalled, @@ -444,20 +467,6 @@ export const { ipaCLIPVisionModelChanged, ipaWeightChanged, ipaBeginEndStepPctChanged, - // Control Adapters - caAdded, - caAllDeleted, - caOpacityChanged, - caRecalled, - caImageChanged, - caProcessedImageChanged, - caModelChanged, - caControlModeChanged, - caProcessorConfigChanged, - caFilterChanged, - caProcessorPendingBatchIdChanged, - caWeightChanged, - caBeginEndStepPctChanged, // Regions rgAdded, rgRecalled, @@ -465,7 +474,6 @@ export const { rgPositivePromptChanged, rgNegativePromptChanged, rgFillChanged, - rgImageCacheChanged, rgAutoNegativeChanged, rgIPAdapterAdded, rgIPAdapterDeleted, @@ -522,7 +530,6 @@ export const { // Inpaint mask imRecalled, imFillChanged, - imImageCacheChanged, // Staging sessionStartedStaging, sessionImageStaged, diff --git a/invokeai/frontend/web/src/features/controlLayers/store/controlAdaptersReducers.ts b/invokeai/frontend/web/src/features/controlLayers/store/controlAdaptersReducers.ts deleted file mode 100644 index a71b2fcedb..0000000000 --- a/invokeai/frontend/web/src/features/controlLayers/store/controlAdaptersReducers.ts +++ /dev/null @@ -1,197 +0,0 @@ -import type { PayloadAction, SliceCaseReducers } from '@reduxjs/toolkit'; -import { zModelIdentifierField } from 'features/nodes/types/common'; -import { isEqual } from 'lodash-es'; -import type { ControlNetModelConfig, ImageDTO, T2IAdapterModelConfig } from 'services/api/types'; -import { assert } from 'tsafe'; -import { v4 as uuidv4 } from 'uuid'; - -import type { - CanvasControlAdapterState, - CanvasControlNetState, - CanvasT2IAdapterState, - CanvasV2State, - ControlModeV2, - ControlNetConfig, - Filter, - FilterConfig, - T2IAdapterConfig, -} from './types'; -import { buildControlAdapterProcessorV2, imageDTOToImageObject } from './types'; - -export const selectCA = (state: CanvasV2State, id: string) => state.controlAdapters.entities.find((ca) => ca.id === id); -export const selectCAOrThrow = (state: CanvasV2State, id: string) => { - const ca = selectCA(state, id); - assert(ca, `Control Adapter with id ${id} not found`); - return ca; -}; - -export const controlAdaptersReducers = { - caAdded: { - reducer: (state, action: PayloadAction<{ id: string; config: ControlNetConfig | T2IAdapterConfig }>) => { - const { id, config } = action.payload; - state.controlAdapters.entities.push({ - id, - type: 'control_adapter', - position: { x: 0, y: 0 }, - bbox: null, - bboxNeedsUpdate: false, - isEnabled: true, - opacity: 1, - filters: ['LightnessToAlphaFilter'], - processorPendingBatchId: null, - ...config, - }); - state.selectedEntityIdentifier = { type: 'control_adapter', id }; - }, - prepare: (payload: { config: ControlNetConfig | T2IAdapterConfig }) => ({ - payload: { id: uuidv4(), ...payload }, - }), - }, - caRecalled: (state, action: PayloadAction<{ data: CanvasControlAdapterState }>) => { - const { data } = action.payload; - state.controlAdapters.entities.push(data); - state.selectedEntityIdentifier = { type: 'control_adapter', id: data.id }; - }, - caAllDeleted: (state) => { - state.controlAdapters.entities = []; - }, - caOpacityChanged: (state, action: PayloadAction<{ id: string; opacity: number }>) => { - const { id, opacity } = action.payload; - const ca = selectCA(state, id); - if (!ca) { - return; - } - ca.opacity = opacity; - }, - caImageChanged: { - reducer: (state, action: PayloadAction<{ id: string; imageDTO: ImageDTO | null; objectId: string }>) => { - const { id, imageDTO, objectId } = action.payload; - const ca = selectCA(state, id); - if (!ca) { - return; - } - ca.bbox = null; - ca.bboxNeedsUpdate = true; - ca.isEnabled = true; - if (imageDTO) { - const newImageObject = imageDTOToImageObject(imageDTO, { filters: ca.filters }); - if (isEqual(newImageObject, ca.imageObject)) { - return; - } - ca.imageObject = newImageObject; - ca.processedImageObject = null; - } else { - ca.imageObject = null; - ca.processedImageObject = null; - } - }, - prepare: (payload: { id: string; imageDTO: ImageDTO | null }) => ({ payload: { ...payload, objectId: uuidv4() } }), - }, - caProcessedImageChanged: { - reducer: (state, action: PayloadAction<{ id: string; imageDTO: ImageDTO | null; objectId: string }>) => { - const { id, imageDTO, objectId } = action.payload; - const ca = selectCA(state, id); - if (!ca) { - return; - } - ca.bbox = null; - ca.bboxNeedsUpdate = true; - ca.isEnabled = true; - ca.processedImageObject = imageDTO ? imageDTOToImageObject(imageDTO, { filters: ca.filters }) : null; - }, - prepare: (payload: { id: string; imageDTO: ImageDTO | null }) => ({ payload: { ...payload, objectId: uuidv4() } }), - }, - caModelChanged: ( - state, - action: PayloadAction<{ - id: string; - modelConfig: ControlNetModelConfig | T2IAdapterModelConfig | null; - }> - ) => { - const { id, modelConfig } = action.payload; - const ca = selectCA(state, id); - if (!ca) { - return; - } - if (!modelConfig) { - ca.model = null; - return; - } - ca.model = zModelIdentifierField.parse(modelConfig); - - const candidateProcessorConfig = buildControlAdapterProcessorV2(modelConfig); - if (candidateProcessorConfig?.type !== ca.processorConfig?.type) { - // The processor has changed. For example, the previous model was a Canny model and the new model is a Depth - // model. We need to use the new processor. - ca.processedImageObject = null; - ca.processorConfig = candidateProcessorConfig; - } - - // We may need to convert the CA to match the model - if (ca.adapterType === 't2i_adapter' && ca.model.type === 'controlnet') { - const convertedCA: CanvasControlNetState = { ...ca, adapterType: 'controlnet', controlMode: 'balanced' }; - state.controlAdapters.entities.splice(state.controlAdapters.entities.indexOf(ca), 1, convertedCA); - } else if (ca.adapterType === 'controlnet' && ca.model.type === 't2i_adapter') { - const { controlMode: _, ...rest } = ca; - const convertedCA: CanvasT2IAdapterState = { ...rest, adapterType: 't2i_adapter' }; - state.controlAdapters.entities.splice(state.controlAdapters.entities.indexOf(ca), 1, convertedCA); - } - }, - caControlModeChanged: (state, action: PayloadAction<{ id: string; controlMode: ControlModeV2 }>) => { - const { id, controlMode } = action.payload; - const ca = selectCA(state, id); - if (!ca || ca.adapterType !== 'controlnet') { - return; - } - ca.controlMode = controlMode; - }, - caProcessorConfigChanged: (state, action: PayloadAction<{ id: string; processorConfig: FilterConfig | null }>) => { - const { id, processorConfig } = action.payload; - const ca = selectCA(state, id); - if (!ca) { - return; - } - ca.processorConfig = processorConfig; - if (!processorConfig) { - ca.processedImageObject = null; - } - }, - caFilterChanged: (state, action: PayloadAction<{ id: string; filters: Filter[] }>) => { - const { id, filters } = action.payload; - const ca = selectCA(state, id); - if (!ca) { - return; - } - ca.filters = filters; - if (ca.imageObject) { - ca.imageObject.filters = filters; - } - if (ca.processedImageObject) { - ca.processedImageObject.filters = filters; - } - }, - caProcessorPendingBatchIdChanged: (state, action: PayloadAction<{ id: string; batchId: string | null }>) => { - const { id, batchId } = action.payload; - const ca = selectCA(state, id); - if (!ca) { - return; - } - ca.processorPendingBatchId = batchId; - }, - caWeightChanged: (state, action: PayloadAction<{ id: string; weight: number }>) => { - const { id, weight } = action.payload; - const ca = selectCA(state, id); - if (!ca) { - return; - } - ca.weight = weight; - }, - caBeginEndStepPctChanged: (state, action: PayloadAction<{ id: string; beginEndStepPct: [number, number] }>) => { - const { id, beginEndStepPct } = action.payload; - const ca = selectCA(state, id); - if (!ca) { - return; - } - ca.beginEndStepPct = beginEndStepPct; - }, -} satisfies SliceCaseReducers; diff --git a/invokeai/frontend/web/src/features/controlLayers/store/inpaintMaskReducers.ts b/invokeai/frontend/web/src/features/controlLayers/store/inpaintMaskReducers.ts index ae7e52805d..2950dc60b7 100644 --- a/invokeai/frontend/web/src/features/controlLayers/store/inpaintMaskReducers.ts +++ b/invokeai/frontend/web/src/features/controlLayers/store/inpaintMaskReducers.ts @@ -1,7 +1,5 @@ import type { PayloadAction, SliceCaseReducers } from '@reduxjs/toolkit'; import type { CanvasInpaintMaskState, CanvasV2State } from 'features/controlLayers/store/types'; -import { imageDTOToImageWithDims } from 'features/controlLayers/store/types'; -import type { ImageDTO } from 'services/api/types'; import type { RgbColor } from './types'; @@ -15,8 +13,4 @@ export const inpaintMaskReducers = { const { fill } = action.payload; state.inpaintMask.fill = fill; }, - imImageCacheChanged: (state, action: PayloadAction<{ imageDTO: ImageDTO | null }>) => { - const { imageDTO } = action.payload; - state.inpaintMask.imageCache = imageDTO ? imageDTOToImageWithDims(imageDTO) : null; - }, } satisfies SliceCaseReducers; diff --git a/invokeai/frontend/web/src/features/controlLayers/store/layersReducers.ts b/invokeai/frontend/web/src/features/controlLayers/store/layersReducers.ts index 1d0407ec94..8348848168 100644 --- a/invokeai/frontend/web/src/features/controlLayers/store/layersReducers.ts +++ b/invokeai/frontend/web/src/features/controlLayers/store/layersReducers.ts @@ -1,12 +1,11 @@ import type { PayloadAction, SliceCaseReducers } from '@reduxjs/toolkit'; import { getPrefixedId } from 'features/controlLayers/konva/util'; import { zModelIdentifierField } from 'features/nodes/types/common'; -import { merge } from 'lodash-es'; -import type { ControlNetModelConfig, ImageDTO, T2IAdapterModelConfig } from 'services/api/types'; +import { isEqual, merge } from 'lodash-es'; +import type { ControlNetModelConfig, T2IAdapterModelConfig } from 'services/api/types'; import { assert } from 'tsafe'; -import type { CanvasLayerState, CanvasV2State, ControlModeV2, ControlNetConfig, T2IAdapterConfig } from './types'; -import { imageDTOToImageWithDims } from './types'; +import type { CanvasLayerState, CanvasV2State, ControlModeV2, ControlNetConfig, Rect, T2IAdapterConfig } from './types'; export const selectLayer = (state: CanvasV2State, id: string) => state.layers.entities.find((layer) => layer.id === id); export const selectLayerOrThrow = (state: CanvasV2State, id: string) => { @@ -29,7 +28,7 @@ export const layersReducers = { objects: [], opacity: 1, position: { x: 0, y: 0 }, - imageCache: null, + rasterizationCache: [], controlAdapter: null, }; merge(layer, overrides); @@ -37,7 +36,11 @@ export const layersReducers = { if (isSelected) { state.selectedEntityIdentifier = { type: 'layer', id }; } - state.layers.imageCache = null; + + if (layer.objects.length > 0) { + // This new layer will change the composite layer's image data. Invalidate the cache. + state.layers.compositeRasterizationCache = []; + } }, prepare: (payload: { overrides?: Partial; isSelected?: boolean }) => ({ payload: { ...payload, id: getPrefixedId('layer') }, @@ -47,24 +50,20 @@ export const layersReducers = { const { data } = action.payload; state.layers.entities.push(data); state.selectedEntityIdentifier = { type: 'layer', id: data.id }; - state.layers.imageCache = null; + if (data.objects.length > 0) { + // This new layer will change the composite layer's image data. Invalidate the cache. + state.layers.compositeRasterizationCache = []; + } }, layerAllDeleted: (state) => { state.layers.entities = []; - state.layers.imageCache = null; + state.layers.compositeRasterizationCache = []; }, - layerOpacityChanged: (state, action: PayloadAction<{ id: string; opacity: number }>) => { - const { id, opacity } = action.payload; - const layer = selectLayer(state, id); - if (!layer) { - return; - } - layer.opacity = opacity; - state.layers.imageCache = null; - }, - layerImageCacheChanged: (state, action: PayloadAction<{ imageDTO: ImageDTO | null }>) => { - const { imageDTO } = action.payload; - state.layers.imageCache = imageDTO ? imageDTOToImageWithDims(imageDTO) : null; + layerCompositeRasterized: (state, action: PayloadAction<{ imageName: string; rect: Rect }>) => { + state.layers.compositeRasterizationCache = state.layers.compositeRasterizationCache.filter( + (cache) => !isEqual(cache.rect, action.payload.rect) + ); + state.layers.compositeRasterizationCache.push(action.payload); }, layerUsedAsControlChanged: ( state, @@ -76,6 +75,8 @@ export const layersReducers = { return; } layer.controlAdapter = controlAdapter; + // The composite layer's image data will change when the layer is used as control (or not). Invalidate the cache. + state.layers.compositeRasterizationCache = []; }, layerControlAdapterModelChanged: ( state, diff --git a/invokeai/frontend/web/src/features/controlLayers/store/regionsReducers.ts b/invokeai/frontend/web/src/features/controlLayers/store/regionsReducers.ts index 372d570e53..fa7a84735e 100644 --- a/invokeai/frontend/web/src/features/controlLayers/store/regionsReducers.ts +++ b/invokeai/frontend/web/src/features/controlLayers/store/regionsReducers.ts @@ -54,7 +54,7 @@ export const regionsReducers = { positivePrompt: '', negativePrompt: null, ipAdapters: [], - imageCache: null, + rasterizationCache: [], }; state.regions.entities.push(rg); state.selectedEntityIdentifier = { type: 'regional_guidance', id }; @@ -93,14 +93,6 @@ export const regionsReducers = { } rg.fill = fill; }, - rgImageCacheChanged: (state, action: PayloadAction<{ id: string; imageDTO: ImageDTO }>) => { - const { id, imageDTO } = action.payload; - const rg = selectRG(state, id); - if (!rg) { - return; - } - rg.imageCache = imageDTO.image_name; - }, rgAutoNegativeChanged: (state, action: PayloadAction<{ id: string; autoNegative: ParameterAutoNegative }>) => { const { id, autoNegative } = action.payload; const rg = selectRG(state, id); diff --git a/invokeai/frontend/web/src/features/controlLayers/store/selectors.ts b/invokeai/frontend/web/src/features/controlLayers/store/selectors.ts index 4a3fd7812d..b52aca28ae 100644 --- a/invokeai/frontend/web/src/features/controlLayers/store/selectors.ts +++ b/invokeai/frontend/web/src/features/controlLayers/store/selectors.ts @@ -5,7 +5,7 @@ import { getOptimalDimension } from 'features/parameters/util/optimalDimension'; export const selectEntityCount = createSelector(selectCanvasV2Slice, (canvasV2) => { return ( canvasV2.regions.entities.length + - canvasV2.controlAdapters.entities.length + + // canvasV2.controlAdapters.entities.length + canvasV2.ipAdapters.entities.length + canvasV2.layers.entities.length ); diff --git a/invokeai/frontend/web/src/features/controlLayers/store/types.ts b/invokeai/frontend/web/src/features/controlLayers/store/types.ts index 1705d5a3c6..a19b694670 100644 --- a/invokeai/frontend/web/src/features/controlLayers/store/types.ts +++ b/invokeai/frontend/web/src/features/controlLayers/store/types.ts @@ -639,6 +639,12 @@ const zMaskObject = z }) .pipe(z.discriminatedUnion('type', [zCanvasBrushLineState, zCanvasEraserLineState, zCanvasRectState])); +const zImageCache = z.object({ + imageName: z.string(), + rect: zRect, +}); +export type ImageCache = z.infer; + export const zCanvasRegionalGuidanceState = z.object({ id: zId, type: z.literal('regional_guidance'), @@ -650,7 +656,7 @@ export const zCanvasRegionalGuidanceState = z.object({ negativePrompt: zParameterNegativePrompt.nullable(), ipAdapters: z.array(zCanvasIPAdapterState), autoNegative: zAutoNegative, - imageCache: z.string().min(1).nullable(), + rasterizationCache: z.array(zImageCache), }); export type CanvasRegionalGuidanceState = z.infer; @@ -670,7 +676,7 @@ const zCanvasInpaintMaskState = z.object({ position: zCoordinate, fill: zRgbColor, objects: z.array(zCanvasObjectState), - imageCache: z.string().min(1).nullable(), + rasterizationCache: z.array(zImageCache), }); export type CanvasInpaintMaskState = z.infer; @@ -729,7 +735,7 @@ export const zCanvasLayerState = z.object({ position: zCoordinate, opacity: zOpacity, objects: z.array(zCanvasObjectState), - imageCache: z.string().min(1).nullable(), + rasterizationCache: z.array(zImageCache), controlAdapter: z.discriminatedUnion('type', [zControlNetConfig, zT2IAdapterConfig]).nullable(), }); export type CanvasLayerState = z.infer; @@ -826,11 +832,7 @@ export type CanvasV2State = { _version: 3; selectedEntityIdentifier: CanvasEntityIdentifier | null; inpaintMask: CanvasInpaintMaskState; - layers: { - imageCache: ImageWithDims | null; - entities: CanvasLayerState[]; - }; - controlAdapters: { entities: CanvasControlAdapterState[] }; + layers: { entities: CanvasLayerState[]; compositeRasterizationCache: ImageCache[] }; ipAdapters: { entities: CanvasIPAdapterState[] }; regions: { entities: CanvasRegionalGuidanceState[] }; loras: LoRA[]; @@ -938,7 +940,7 @@ export type EntityRectAddedPayload = { entityIdentifier: CanvasEntityIdentifier; export type EntityRasterizedPayload = { entityIdentifier: CanvasEntityIdentifier; imageObject: CanvasImageState; - position: Coordinate; + rect: Rect; }; export type ImageObjectAddedArg = { id: string; imageDTO: ImageDTO; position?: Coordinate }; diff --git a/invokeai/frontend/web/src/features/metadata/util/recallers.ts b/invokeai/frontend/web/src/features/metadata/util/recallers.ts index 5ea7fb0ad0..54196820d4 100644 --- a/invokeai/frontend/web/src/features/metadata/util/recallers.ts +++ b/invokeai/frontend/web/src/features/metadata/util/recallers.ts @@ -13,7 +13,7 @@ import { import { bboxHeightChanged, bboxWidthChanged, - caRecalled, + // caRecalled, ipaRecalled, layerAllDeleted, layerRecalled, @@ -43,8 +43,8 @@ import type { CanvasControlAdapterState, CanvasIPAdapterState, CanvasLayerState, - LoRA, CanvasRegionalGuidanceState, + LoRA, } from 'features/controlLayers/store/types'; import { setHrfEnabled, setHrfMethod, setHrfStrength } from 'features/hrf/store/hrfSlice'; import type { @@ -271,7 +271,7 @@ const recallCA: MetadataRecallFunc = async (ca) => { } // No clobber clone.id = getCAId(uuidv4()); - dispatch(caRecalled({ data: clone })); + // dispatch(caRecalled({ data: clone })); return; }; diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/generation/addControlAdapters.ts b/invokeai/frontend/web/src/features/nodes/util/graph/generation/addControlAdapters.ts index b4a8b0b8d0..8b8023cca3 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/generation/addControlAdapters.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/generation/addControlAdapters.ts @@ -26,10 +26,11 @@ export const addControlAdapters = async ( const layersWithValidControlAdapters = layers .filter((layer) => layer.isEnabled) .filter((layer) => doesLayerHaveValidControlAdapter(layer, base)); + for (const layer of layersWithValidControlAdapters) { const adapter = manager.layers.get(layer.id); assert(adapter, 'Adapter not found'); - const imageDTO = await adapter.renderer.getImageDTO({ rect: bbox, is_intermediate: true, category: 'control' }); + const imageDTO = await adapter.renderer.rasterize(bbox); if (layer.controlAdapter.type === 'controlnet') { await addControlNetToGraph(g, layer, imageDTO, denoise); } else { diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/generation/addInpaint.ts b/invokeai/frontend/web/src/features/nodes/util/graph/generation/addInpaint.ts index 75cf71dcdf..5f85274ac5 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/generation/addInpaint.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/generation/addInpaint.ts @@ -22,7 +22,7 @@ export const addInpaint = async ( denoise.denoising_start = denoising_start; const initialImage = await manager.getCompositeLayerImageDTO(bbox.rect); - const maskImage = await manager.getInpaintMaskImageDTO(bbox.rect); + const maskImage = await manager.inpaintMask.renderer.rasterize(bbox.rect); if (!isEqual(scaledSize, originalSize)) { // Scale before processing requires some resizing diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/generation/addOutpaint.ts b/invokeai/frontend/web/src/features/nodes/util/graph/generation/addOutpaint.ts index fcf5b77393..7e4296cb77 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/generation/addOutpaint.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/generation/addOutpaint.ts @@ -23,7 +23,7 @@ export const addOutpaint = async ( denoise.denoising_start = denoising_start; const initialImage = await manager.getCompositeLayerImageDTO(bbox.rect); - const maskImage = await manager.getInpaintMaskImageDTO(bbox.rect); + const maskImage = await manager.inpaintMask.renderer.rasterize(bbox.rect); const infill = getInfill(g, compositing); if (!isEqual(scaledSize, originalSize)) { diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/generation/addRegions.ts b/invokeai/frontend/web/src/features/nodes/util/graph/generation/addRegions.ts index 5e65a3a669..847348cfb9 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/generation/addRegions.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/generation/addRegions.ts @@ -43,15 +43,16 @@ export const addRegions = async ( const validRegions = regions.filter((rg) => isValidRegion(rg, base)); for (const region of validRegions) { - // Upload the mask image, or get the cached image if it exists - const { image_name } = await manager.getRegionMaskImageDTO(region.id, bbox); + const adapter = manager.regions.get(region.id); + assert(adapter, 'Adapter not found'); + const imageDTO = await adapter.renderer.rasterize(bbox); // The main mask-to-tensor node const maskToTensor = g.addNode({ id: `${PROMPT_REGION_MASK_TO_TENSOR_PREFIX}_${region.id}`, type: 'alpha_mask_to_tensor', image: { - image_name, + image_name: imageDTO.image_name, }, });