From 6f5f3381f973aec422ef6609fc539d28a2023e34 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Fri, 26 Apr 2024 16:56:07 +1000 Subject: [PATCH] feat(ui): revise internal state for RCC --- .../listeners/promptChanged.ts | 2 +- .../src/common/hooks/useIsReadyToEnqueue.ts | 2 +- .../util/graph/addIPAdapterToLinearGraph.ts | 4 +- .../util/graph/addRegionalPromptsToGraph.ts | 4 +- .../graph/buildCanvasImageToImageGraph.ts | 2 +- .../util/graph/buildCanvasInpaintGraph.ts | 2 +- .../util/graph/buildCanvasOutpaintGraph.ts | 2 +- .../graph/buildCanvasSDXLImageToImageGraph.ts | 2 +- .../util/graph/buildCanvasSDXLInpaintGraph.ts | 2 +- .../graph/buildCanvasSDXLOutpaintGraph.ts | 2 +- .../graph/buildCanvasSDXLTextToImageGraph.ts | 2 +- .../util/graph/buildCanvasTextToImageGraph.ts | 2 +- .../util/graph/buildLinearBatchConfig.ts | 2 +- .../graph/buildLinearImageToImageGraph.ts | 2 +- .../graph/buildLinearSDXLImageToImageGraph.ts | 2 +- .../graph/buildLinearSDXLTextToImageGraph.ts | 2 +- .../util/graph/buildLinearTextToImageGraph.ts | 2 +- .../nodes/util/graph/graphBuilderUtils.ts | 2 +- .../components/Core/ParamNegativePrompt.tsx | 2 +- .../components/Core/ParamPositivePrompt.tsx | 2 +- .../queue/components/QueueButtonTooltip.tsx | 2 +- .../components/AddLayerButton.tsx | 2 +- .../components/AddPromptButtons.tsx | 4 +- .../RPLayerAutoNegativeCheckbox.tsx | 4 +- .../components/RPLayerColorPicker.tsx | 4 +- .../components/RPLayerIPAdapterList.tsx | 4 +- .../components/RPLayerListItem.tsx | 4 +- .../components/RPLayerMenu.tsx | 4 +- .../RegionalPromptsPanelContent.tsx | 4 +- .../components/StageComponent.tsx | 4 +- .../components/ToolChooser.tsx | 2 +- .../regionalPrompts/hooks/layerStateHooks.ts | 8 +- .../hooks/useRegionalControlTitle.ts | 4 +- .../store/regionalPromptsSlice.ts | 161 ++++++++++-------- .../src/features/regionalPrompts/util/bbox.ts | 4 +- .../regionalPrompts/util/getLayerBlobs.ts | 6 +- .../regionalPrompts/util/renderers.ts | 46 ++--- .../ParamSDXLNegativeStylePrompt.tsx | 2 +- .../ParamSDXLPositiveStylePrompt.tsx | 2 +- .../SDXLPrompts/SDXLConcatButton.tsx | 2 +- .../components/SDXLPrompts/SDXLPrompts.tsx | 2 +- .../ControlSettingsAccordion.tsx | 6 +- 42 files changed, 170 insertions(+), 155 deletions(-) diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/promptChanged.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/promptChanged.ts index 93926f4b08..db38a0aa2e 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/promptChanged.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/promptChanged.ts @@ -28,7 +28,7 @@ export const addDynamicPromptsListener = (startAppListening: AppStartListening) effect: async (action, { dispatch, getState, cancelActiveListeners, delay }) => { cancelActiveListeners(); const state = getState(); - const { positivePrompt } = state.regionalPrompts.present.baseLayer; + const { positivePrompt } = state.regionalPrompts.present; const { maxPrompts } = state.dynamicPrompts; if (state.config.disabledFeatures.includes('dynamicPrompting')) { diff --git a/invokeai/frontend/web/src/common/hooks/useIsReadyToEnqueue.ts b/invokeai/frontend/web/src/common/hooks/useIsReadyToEnqueue.ts index fa4c1aaf45..24abcd631d 100644 --- a/invokeai/frontend/web/src/common/hooks/useIsReadyToEnqueue.ts +++ b/invokeai/frontend/web/src/common/hooks/useIsReadyToEnqueue.ts @@ -29,7 +29,7 @@ const selector = createMemoizedSelector( ], (controlAdapters, generation, system, nodes, dynamicPrompts, regionalPrompts, activeTabName) => { const { initialImage, model } = generation; - const { positivePrompt } = regionalPrompts.present.baseLayer; + const { positivePrompt } = regionalPrompts.present; const { isConnected } = system; diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/addIPAdapterToLinearGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graph/addIPAdapterToLinearGraph.ts index 4a287b5335..5f39fd93d8 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/addIPAdapterToLinearGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/addIPAdapterToLinearGraph.ts @@ -2,7 +2,7 @@ import type { RootState } from 'app/store/store'; import { selectValidIPAdapters } from 'features/controlAdapters/store/controlAdaptersSlice'; import type { IPAdapterConfig } from 'features/controlAdapters/store/types'; import type { ImageField } from 'features/nodes/types/common'; -import { isVectorMaskLayer } from 'features/regionalPrompts/store/regionalPromptsSlice'; +import { isMaskedGuidanceLayer } from 'features/regionalPrompts/store/regionalPromptsSlice'; import { differenceBy } from 'lodash-es'; import type { CollectInvocation, @@ -29,7 +29,7 @@ export const addIPAdapterToLinearGraph = async ( }); const regionalIPAdapterIds = state.regionalPrompts.present.layers - .filter(isVectorMaskLayer) + .filter(isMaskedGuidanceLayer) .map((l) => l.ipAdapterIds) .flat(); diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/addRegionalPromptsToGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graph/addRegionalPromptsToGraph.ts index 8d7a3a6c9a..d7451d3f1f 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/addRegionalPromptsToGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/addRegionalPromptsToGraph.ts @@ -13,7 +13,7 @@ import { PROMPT_REGION_POSITIVE_COND_INVERTED_PREFIX, PROMPT_REGION_POSITIVE_COND_PREFIX, } from 'features/nodes/util/graph/constants'; -import { isVectorMaskLayer } from 'features/regionalPrompts/store/regionalPromptsSlice'; +import { isMaskedGuidanceLayer } from 'features/regionalPrompts/store/regionalPromptsSlice'; import { getRegionalPromptLayerBlobs } from 'features/regionalPrompts/util/getLayerBlobs'; import { size } from 'lodash-es'; import { imagesApi } from 'services/api/endpoints/images'; @@ -29,7 +29,7 @@ export const addRegionalPromptsToGraph = async (state: RootState, graph: NonNull const layers = state.regionalPrompts.present.layers // Only support vector mask layers now // TODO: Image masks - .filter(isVectorMaskLayer) + .filter(isMaskedGuidanceLayer) // Only visible layers are rendered on the canvas .filter((l) => l.isVisible) // Only layers with prompts get added to the graph diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasImageToImageGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasImageToImageGraph.ts index e0844057ba..144db94f3c 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasImageToImageGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasImageToImageGraph.ts @@ -55,7 +55,7 @@ export const buildCanvasImageToImageGraph = async ( seamlessXAxis, seamlessYAxis, } = state.generation; - const { positivePrompt, negativePrompt } = state.regionalPrompts.present.baseLayer; + const { positivePrompt, negativePrompt } = state.regionalPrompts.present; // The bounding box determines width and height, not the width and height params const { width, height } = state.canvas.boundingBoxDimensions; diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasInpaintGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasInpaintGraph.ts index 6c1c954f58..49cd590780 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasInpaintGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasInpaintGraph.ts @@ -64,7 +64,7 @@ export const buildCanvasInpaintGraph = async ( canvasCoherenceEdgeSize, maskBlur, } = state.generation; - const { positivePrompt, negativePrompt } = state.regionalPrompts.present.baseLayer; + const { positivePrompt, negativePrompt } = state.regionalPrompts.present; if (!model) { log.error('No model found in state'); diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasOutpaintGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasOutpaintGraph.ts index 4a9beef3eb..c94b07d395 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasOutpaintGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasOutpaintGraph.ts @@ -76,7 +76,7 @@ export const buildCanvasOutpaintGraph = async ( canvasCoherenceEdgeSize, maskBlur, } = state.generation; - const { positivePrompt, negativePrompt } = state.regionalPrompts.present.baseLayer; + const { positivePrompt, negativePrompt } = state.regionalPrompts.present; if (!model) { log.error('No model found in state'); diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasSDXLImageToImageGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasSDXLImageToImageGraph.ts index 8e06dde054..7b29d0a8fb 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasSDXLImageToImageGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasSDXLImageToImageGraph.ts @@ -55,7 +55,7 @@ export const buildCanvasSDXLImageToImageGraph = async ( seamlessYAxis, img2imgStrength: strength, } = state.generation; - const { positivePrompt, negativePrompt } = state.regionalPrompts.present.baseLayer; + const { positivePrompt, negativePrompt } = state.regionalPrompts.present; const { refinerModel, refinerStart } = state.sdxl; diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasSDXLInpaintGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasSDXLInpaintGraph.ts index ee3d0897b8..4b1fd4575a 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasSDXLInpaintGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasSDXLInpaintGraph.ts @@ -64,7 +64,7 @@ export const buildCanvasSDXLInpaintGraph = async ( canvasCoherenceEdgeSize, maskBlur, } = state.generation; - const { positivePrompt, negativePrompt } = state.regionalPrompts.present.baseLayer; + const { positivePrompt, negativePrompt } = state.regionalPrompts.present; const { refinerModel, refinerStart } = state.sdxl; diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasSDXLOutpaintGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasSDXLOutpaintGraph.ts index ddcbb168ef..0561976edb 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasSDXLOutpaintGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasSDXLOutpaintGraph.ts @@ -76,7 +76,7 @@ export const buildCanvasSDXLOutpaintGraph = async ( canvasCoherenceEdgeSize, maskBlur, } = state.generation; - const { positivePrompt, negativePrompt } = state.regionalPrompts.present.baseLayer; + const { positivePrompt, negativePrompt } = state.regionalPrompts.present; const { refinerModel, refinerStart } = state.sdxl; diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasSDXLTextToImageGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasSDXLTextToImageGraph.ts index 9365faee81..3f890e02ef 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasSDXLTextToImageGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasSDXLTextToImageGraph.ts @@ -44,7 +44,7 @@ export const buildCanvasSDXLTextToImageGraph = async (state: RootState): Promise seamlessXAxis, seamlessYAxis, } = state.generation; - const { positivePrompt, negativePrompt } = state.regionalPrompts.present.baseLayer; + const { positivePrompt, negativePrompt } = state.regionalPrompts.present; // The bounding box determines width and height, not the width and height params const { width, height } = state.canvas.boundingBoxDimensions; diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasTextToImageGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasTextToImageGraph.ts index 57529f9a02..4458720be7 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasTextToImageGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasTextToImageGraph.ts @@ -44,7 +44,7 @@ export const buildCanvasTextToImageGraph = async (state: RootState): Promise { const { iterations, model, shouldRandomizeSeed, seed } = state.generation; - const { shouldConcatPrompts } = state.regionalPrompts.present.baseLayer; + const { shouldConcatPrompts } = state.regionalPrompts.present; const { prompts, seedBehaviour } = state.dynamicPrompts; const data: Batch['data'] = []; diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/buildLinearImageToImageGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graph/buildLinearImageToImageGraph.ts index 5c9b6b96db..1a890ed57a 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/buildLinearImageToImageGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/buildLinearImageToImageGraph.ts @@ -53,7 +53,7 @@ export const buildLinearImageToImageGraph = async (state: RootState): Promise { */ export const getSDXLStylePrompts = (state: RootState): { positiveStylePrompt: string; negativeStylePrompt: string } => { const { positivePrompt, negativePrompt, positivePrompt2, negativePrompt2, shouldConcatPrompts } = - state.regionalPrompts.present.baseLayer; + state.regionalPrompts.present; return { positiveStylePrompt: shouldConcatPrompts ? positivePrompt : positivePrompt2, diff --git a/invokeai/frontend/web/src/features/parameters/components/Core/ParamNegativePrompt.tsx b/invokeai/frontend/web/src/features/parameters/components/Core/ParamNegativePrompt.tsx index 70e36e8dcc..077bdca108 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Core/ParamNegativePrompt.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Core/ParamNegativePrompt.tsx @@ -10,7 +10,7 @@ import { useTranslation } from 'react-i18next'; export const ParamNegativePrompt = memo(() => { const dispatch = useAppDispatch(); - const prompt = useAppSelector((s) => s.regionalPrompts.present.baseLayer.negativePrompt); + const prompt = useAppSelector((s) => s.regionalPrompts.present.negativePrompt); const textareaRef = useRef(null); const { t } = useTranslation(); const _onChange = useCallback( diff --git a/invokeai/frontend/web/src/features/parameters/components/Core/ParamPositivePrompt.tsx b/invokeai/frontend/web/src/features/parameters/components/Core/ParamPositivePrompt.tsx index 732a50fa1b..90b6ae3093 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Core/ParamPositivePrompt.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Core/ParamPositivePrompt.tsx @@ -14,7 +14,7 @@ import { useTranslation } from 'react-i18next'; export const ParamPositivePrompt = memo(() => { const dispatch = useAppDispatch(); - const prompt = useAppSelector((s) => s.regionalPrompts.present.baseLayer.positivePrompt); + const prompt = useAppSelector((s) => s.regionalPrompts.present.positivePrompt); const baseModel = useAppSelector((s) => s.generation.model)?.base; const textareaRef = useRef(null); diff --git a/invokeai/frontend/web/src/features/queue/components/QueueButtonTooltip.tsx b/invokeai/frontend/web/src/features/queue/components/QueueButtonTooltip.tsx index 3ea7d95309..e020782aa1 100644 --- a/invokeai/frontend/web/src/features/queue/components/QueueButtonTooltip.tsx +++ b/invokeai/frontend/web/src/features/queue/components/QueueButtonTooltip.tsx @@ -14,7 +14,7 @@ const selectPromptsCount = createSelector( selectRegionalPromptsSlice, selectDynamicPromptsSlice, (regionalPrompts, dynamicPrompts) => - getShouldProcessPrompt(regionalPrompts.present.baseLayer.positivePrompt) ? dynamicPrompts.prompts.length : 1 + getShouldProcessPrompt(regionalPrompts.present.positivePrompt) ? dynamicPrompts.prompts.length : 1 ); type Props = { diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/AddLayerButton.tsx b/invokeai/frontend/web/src/features/regionalPrompts/components/AddLayerButton.tsx index 8acfc18c56..a9b3480b87 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/components/AddLayerButton.tsx +++ b/invokeai/frontend/web/src/features/regionalPrompts/components/AddLayerButton.tsx @@ -9,7 +9,7 @@ export const AddLayerButton = memo(() => { const { t } = useTranslation(); const dispatch = useAppDispatch(); const onClick = useCallback(() => { - dispatch(layerAdded('vector_mask_layer')); + dispatch(layerAdded('masked_guidance_layer')); }, [dispatch]); return ( diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/AddPromptButtons.tsx b/invokeai/frontend/web/src/features/regionalPrompts/components/AddPromptButtons.tsx index 2f1d4e8f4d..db2e35c234 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/components/AddPromptButtons.tsx +++ b/invokeai/frontend/web/src/features/regionalPrompts/components/AddPromptButtons.tsx @@ -2,7 +2,7 @@ import { Button, Flex } from '@invoke-ai/ui-library'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { - isVectorMaskLayer, + isMaskedGuidanceLayer, maskLayerIPAdapterAdded, maskLayerNegativePromptChanged, maskLayerPositivePromptChanged, @@ -23,7 +23,7 @@ export const AddPromptButtons = ({ layerId }: AddPromptButtonProps) => { () => createMemoizedSelector(selectRegionalPromptsSlice, (regionalPrompts) => { const layer = regionalPrompts.present.layers.find((l) => l.id === layerId); - assert(isVectorMaskLayer(layer), `Layer ${layerId} not found or not an RP layer`); + assert(isMaskedGuidanceLayer(layer), `Layer ${layerId} not found or not an RP layer`); return { canAddPositivePrompt: layer.positivePrompt === null, canAddNegativePrompt: layer.negativePrompt === null, diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerAutoNegativeCheckbox.tsx b/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerAutoNegativeCheckbox.tsx index 454c30a6d2..e4e0f86796 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerAutoNegativeCheckbox.tsx +++ b/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerAutoNegativeCheckbox.tsx @@ -2,7 +2,7 @@ import { Checkbox, FormControl, FormLabel } from '@invoke-ai/ui-library'; import { createSelector } from '@reduxjs/toolkit'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { - isVectorMaskLayer, + isMaskedGuidanceLayer, maskLayerAutoNegativeChanged, selectRegionalPromptsSlice, } from 'features/regionalPrompts/store/regionalPromptsSlice'; @@ -20,7 +20,7 @@ const useAutoNegative = (layerId: string) => { () => createSelector(selectRegionalPromptsSlice, (regionalPrompts) => { const layer = regionalPrompts.present.layers.find((l) => l.id === layerId); - assert(isVectorMaskLayer(layer), `Layer ${layerId} not found or not an RP layer`); + assert(isMaskedGuidanceLayer(layer), `Layer ${layerId} not found or not an RP layer`); return layer.autoNegative; }), [layerId] diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerColorPicker.tsx b/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerColorPicker.tsx index 851cd8b13f..81c4b17fdd 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerColorPicker.tsx +++ b/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerColorPicker.tsx @@ -4,7 +4,7 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import RgbColorPicker from 'common/components/RgbColorPicker'; import { rgbColorToString } from 'features/canvas/util/colorToString'; import { - isVectorMaskLayer, + isMaskedGuidanceLayer, maskLayerPreviewColorChanged, selectRegionalPromptsSlice, } from 'features/regionalPrompts/store/regionalPromptsSlice'; @@ -23,7 +23,7 @@ export const RPLayerColorPicker = memo(({ layerId }: Props) => { () => createMemoizedSelector(selectRegionalPromptsSlice, (regionalPrompts) => { const layer = regionalPrompts.present.layers.find((l) => l.id === layerId); - assert(isVectorMaskLayer(layer), `Layer ${layerId} not found or not an vector mask layer`); + assert(isMaskedGuidanceLayer(layer), `Layer ${layerId} not found or not an vector mask layer`); return layer.previewColor; }), [layerId] diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerIPAdapterList.tsx b/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerIPAdapterList.tsx index 2da01de92a..ff2efaa0f9 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerIPAdapterList.tsx +++ b/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerIPAdapterList.tsx @@ -2,7 +2,7 @@ import { Flex } from '@invoke-ai/ui-library'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; import { useAppSelector } from 'app/store/storeHooks'; import ControlAdapterConfig from 'features/controlAdapters/components/ControlAdapterConfig'; -import { isVectorMaskLayer, selectRegionalPromptsSlice } from 'features/regionalPrompts/store/regionalPromptsSlice'; +import { isMaskedGuidanceLayer, selectRegionalPromptsSlice } from 'features/regionalPrompts/store/regionalPromptsSlice'; import { memo, useMemo } from 'react'; import { assert } from 'tsafe'; @@ -14,7 +14,7 @@ export const RPLayerIPAdapterList = memo(({ layerId }: Props) => { const selectIPAdapterIds = useMemo( () => createMemoizedSelector(selectRegionalPromptsSlice, (regionalPrompts) => { - const layer = regionalPrompts.present.layers.filter(isVectorMaskLayer).find((l) => l.id === layerId); + const layer = regionalPrompts.present.layers.filter(isMaskedGuidanceLayer).find((l) => l.id === layerId); assert(layer, `Layer ${layerId} not found`); return layer.ipAdapterIds; }), diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerListItem.tsx b/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerListItem.tsx index ef98a659ac..989ee8c576 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerListItem.tsx +++ b/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerListItem.tsx @@ -11,7 +11,7 @@ import { RPLayerPositivePrompt } from 'features/regionalPrompts/components/RPLay import RPLayerSettingsPopover from 'features/regionalPrompts/components/RPLayerSettingsPopover'; import { RPLayerVisibilityToggle } from 'features/regionalPrompts/components/RPLayerVisibilityToggle'; import { - isVectorMaskLayer, + isMaskedGuidanceLayer, layerSelected, selectRegionalPromptsSlice, } from 'features/regionalPrompts/store/regionalPromptsSlice'; @@ -32,7 +32,7 @@ export const RPLayerListItem = memo(({ layerId }: Props) => { () => createMemoizedSelector(selectRegionalPromptsSlice, (regionalPrompts) => { const layer = regionalPrompts.present.layers.find((l) => l.id === layerId); - assert(isVectorMaskLayer(layer), `Layer ${layerId} not found or not an RP layer`); + assert(isMaskedGuidanceLayer(layer), `Layer ${layerId} not found or not an RP layer`); return { color: rgbColorToString(layer.previewColor), hasPositivePrompt: layer.positivePrompt !== null, diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerMenu.tsx b/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerMenu.tsx index f3c5317e10..665b11e1d9 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerMenu.tsx +++ b/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerMenu.tsx @@ -2,7 +2,7 @@ import { IconButton, Menu, MenuButton, MenuDivider, MenuItem, MenuList } from '@ import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { - isVectorMaskLayer, + isMaskedGuidanceLayer, layerDeleted, layerMovedBackward, layerMovedForward, @@ -37,7 +37,7 @@ export const RPLayerMenu = memo(({ layerId }: Props) => { () => createMemoizedSelector(selectRegionalPromptsSlice, (regionalPrompts) => { const layer = regionalPrompts.present.layers.find((l) => l.id === layerId); - assert(isVectorMaskLayer(layer), `Layer ${layerId} not found or not an RP layer`); + assert(isMaskedGuidanceLayer(layer), `Layer ${layerId} not found or not an RP layer`); const layerIndex = regionalPrompts.present.layers.findIndex((l) => l.id === layerId); const layerCount = regionalPrompts.present.layers.length; return { diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/RegionalPromptsPanelContent.tsx b/invokeai/frontend/web/src/features/regionalPrompts/components/RegionalPromptsPanelContent.tsx index 1fe4d53623..74bc552239 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/components/RegionalPromptsPanelContent.tsx +++ b/invokeai/frontend/web/src/features/regionalPrompts/components/RegionalPromptsPanelContent.tsx @@ -6,12 +6,12 @@ import ScrollableContent from 'common/components/OverlayScrollbars/ScrollableCon import { AddLayerButton } from 'features/regionalPrompts/components/AddLayerButton'; import { DeleteAllLayersButton } from 'features/regionalPrompts/components/DeleteAllLayersButton'; import { RPLayerListItem } from 'features/regionalPrompts/components/RPLayerListItem'; -import { isVectorMaskLayer, selectRegionalPromptsSlice } from 'features/regionalPrompts/store/regionalPromptsSlice'; +import { isMaskedGuidanceLayer, selectRegionalPromptsSlice } from 'features/regionalPrompts/store/regionalPromptsSlice'; import { memo } from 'react'; const selectRPLayerIdsReversed = createMemoizedSelector(selectRegionalPromptsSlice, (regionalPrompts) => regionalPrompts.present.layers - .filter(isVectorMaskLayer) + .filter(isMaskedGuidanceLayer) .map((l) => l.id) .reverse() ); diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/StageComponent.tsx b/invokeai/frontend/web/src/features/regionalPrompts/components/StageComponent.tsx index d6f6eff812..40f02ace5b 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/components/StageComponent.tsx +++ b/invokeai/frontend/web/src/features/regionalPrompts/components/StageComponent.tsx @@ -9,7 +9,7 @@ import { $isMouseOver, $lastMouseDownPos, $tool, - isVectorMaskLayer, + isMaskedGuidanceLayer, layerBboxChanged, layerSelected, layerTranslated, @@ -32,7 +32,7 @@ const selectSelectedLayerColor = createMemoizedSelector(selectRegionalPromptsSli if (!layer) { return null; } - assert(isVectorMaskLayer(layer), `Layer ${regionalPrompts.present.selectedLayerId} is not an RP layer`); + assert(isMaskedGuidanceLayer(layer), `Layer ${regionalPrompts.present.selectedLayerId} is not an RP layer`); return layer.previewColor; }); diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/ToolChooser.tsx b/invokeai/frontend/web/src/features/regionalPrompts/components/ToolChooser.tsx index a79c443a2f..6ba73ccb7b 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/components/ToolChooser.tsx +++ b/invokeai/frontend/web/src/features/regionalPrompts/components/ToolChooser.tsx @@ -41,7 +41,7 @@ export const ToolChooser: React.FC = () => { useHotkeys('shift+c', resetSelectedLayer); const addLayer = useCallback(() => { - dispatch(layerAdded('vector_mask_layer')); + dispatch(layerAdded('masked_guidance_layer')); }, [dispatch]); useHotkeys('shift+a', addLayer); diff --git a/invokeai/frontend/web/src/features/regionalPrompts/hooks/layerStateHooks.ts b/invokeai/frontend/web/src/features/regionalPrompts/hooks/layerStateHooks.ts index 8ca830e228..a2ee3bd320 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/hooks/layerStateHooks.ts +++ b/invokeai/frontend/web/src/features/regionalPrompts/hooks/layerStateHooks.ts @@ -1,6 +1,6 @@ import { createSelector } from '@reduxjs/toolkit'; import { useAppSelector } from 'app/store/storeHooks'; -import { isVectorMaskLayer, selectRegionalPromptsSlice } from 'features/regionalPrompts/store/regionalPromptsSlice'; +import { isMaskedGuidanceLayer, selectRegionalPromptsSlice } from 'features/regionalPrompts/store/regionalPromptsSlice'; import { useMemo } from 'react'; import { assert } from 'tsafe'; @@ -9,7 +9,7 @@ export const useLayerPositivePrompt = (layerId: string) => { () => createSelector(selectRegionalPromptsSlice, (regionalPrompts) => { const layer = regionalPrompts.present.layers.find((l) => l.id === layerId); - assert(isVectorMaskLayer(layer), `Layer ${layerId} not found or not an RP layer`); + assert(isMaskedGuidanceLayer(layer), `Layer ${layerId} not found or not an RP layer`); assert(layer.positivePrompt !== null, `Layer ${layerId} does not have a positive prompt`); return layer.positivePrompt; }), @@ -24,7 +24,7 @@ export const useLayerNegativePrompt = (layerId: string) => { () => createSelector(selectRegionalPromptsSlice, (regionalPrompts) => { const layer = regionalPrompts.present.layers.find((l) => l.id === layerId); - assert(isVectorMaskLayer(layer), `Layer ${layerId} not found or not an RP layer`); + assert(isMaskedGuidanceLayer(layer), `Layer ${layerId} not found or not an RP layer`); assert(layer.negativePrompt !== null, `Layer ${layerId} does not have a negative prompt`); return layer.negativePrompt; }), @@ -39,7 +39,7 @@ export const useLayerIsVisible = (layerId: string) => { () => createSelector(selectRegionalPromptsSlice, (regionalPrompts) => { const layer = regionalPrompts.present.layers.find((l) => l.id === layerId); - assert(isVectorMaskLayer(layer), `Layer ${layerId} not found or not an RP layer`); + assert(isMaskedGuidanceLayer(layer), `Layer ${layerId} not found or not an RP layer`); return layer.isVisible; }), [layerId] diff --git a/invokeai/frontend/web/src/features/regionalPrompts/hooks/useRegionalControlTitle.ts b/invokeai/frontend/web/src/features/regionalPrompts/hooks/useRegionalControlTitle.ts index 24532d2fa1..bb9fd4f5e2 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/hooks/useRegionalControlTitle.ts +++ b/invokeai/frontend/web/src/features/regionalPrompts/hooks/useRegionalControlTitle.ts @@ -1,6 +1,6 @@ import { createSelector } from '@reduxjs/toolkit'; import { useAppSelector } from 'app/store/storeHooks'; -import { isVectorMaskLayer, selectRegionalPromptsSlice } from 'features/regionalPrompts/store/regionalPromptsSlice'; +import { isMaskedGuidanceLayer, selectRegionalPromptsSlice } from 'features/regionalPrompts/store/regionalPromptsSlice'; import { useMemo } from 'react'; import { useTranslation } from 'react-i18next'; @@ -9,7 +9,7 @@ const selectValidLayerCount = createSelector(selectRegionalPromptsSlice, (region return 0; } const validLayers = regionalPrompts.present.layers - .filter(isVectorMaskLayer) + .filter(isMaskedGuidanceLayer) .filter((l) => l.isVisible) .filter((l) => { const hasTextPrompt = Boolean(l.positivePrompt || l.negativePrompt); diff --git a/invokeai/frontend/web/src/features/regionalPrompts/store/regionalPromptsSlice.ts b/invokeai/frontend/web/src/features/regionalPrompts/store/regionalPromptsSlice.ts index 7d809f2695..bdc0f263b9 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/store/regionalPromptsSlice.ts +++ b/invokeai/frontend/web/src/features/regionalPrompts/store/regionalPromptsSlice.ts @@ -5,7 +5,6 @@ import { moveBackward, moveForward, moveToBack, moveToFront } from 'common/util/ import { deepClone } from 'common/util/deepClone'; import { roundToMultiple } from 'common/util/roundDownToMultiple'; import { controlAdapterRemoved, isAnyControlAdapterAdded } from 'features/controlAdapters/store/controlAdaptersSlice'; -import type { ControlAdapterConfig } from 'features/controlAdapters/store/types'; import { calculateNewSize } from 'features/parameters/components/ImageSize/calculateNewSize'; import { initialAspectRatioState } from 'features/parameters/components/ImageSize/constants'; import type { AspectRatioState } from 'features/parameters/components/ImageSize/types'; @@ -51,41 +50,38 @@ export type VectorMaskRect = { type LayerBase = { id: string; + isVisible: boolean; +}; + +type RenderableLayerBase = LayerBase & { x: number; y: number; bbox: IRect | null; bboxNeedsUpdate: boolean; - isVisible: boolean; }; -type ControlLayer = LayerBase & { - type: 'control_layer'; - controlAdapter: ControlAdapterConfig; +type ControlAdapterLayer = RenderableLayerBase & { + type: 'controlnet_layer'; // technically, also t2i adapter layer + controlAdapterId: string; }; -type MaskLayerBase = LayerBase & { - positivePrompt: string | null; - negativePrompt: string | null; // Up to one text prompt per mask +type IPAdapterLayer = LayerBase & { + type: 'ip_adapter_layer'; // technically, also t2i adapter layer + ipAdapterId: string; +}; + +export type MaskedGuidanceLayer = RenderableLayerBase & { + type: 'masked_guidance_layer'; + maskObjects: (VectorMaskLine | VectorMaskRect)[]; + positivePrompt: ParameterPositivePrompt | null; + negativePrompt: ParameterNegativePrompt | null; // Up to one text prompt per mask ipAdapterIds: string[]; // Any number of image prompts previewColor: RgbColor; autoNegative: ParameterAutoNegative; needsPixelBbox: boolean; // Needs the slower pixel-based bbox calculation - set to true when an there is an eraser object }; -export type VectorMaskLayer = MaskLayerBase & { - type: 'vector_mask_layer'; - objects: (VectorMaskLine | VectorMaskRect)[]; -}; - -export type Layer = VectorMaskLayer | ControlLayer; - -type BaseLayerState = { - positivePrompt: ParameterPositivePrompt; - negativePrompt: ParameterNegativePrompt; - positivePrompt2: ParameterPositiveStylePromptSDXL; - negativePrompt2: ParameterNegativeStylePromptSDXL; - shouldConcatPrompts: boolean; -}; +export type Layer = MaskedGuidanceLayer | ControlAdapterLayer | IPAdapterLayer; type RegionalPromptsState = { _version: 1; @@ -94,7 +90,12 @@ type RegionalPromptsState = { brushSize: number; globalMaskLayerOpacity: number; isEnabled: boolean; - baseLayer: BaseLayerState; + positivePrompt: ParameterPositivePrompt; + negativePrompt: ParameterNegativePrompt; + positivePrompt2: ParameterPositiveStylePromptSDXL; + negativePrompt2: ParameterNegativeStylePromptSDXL; + shouldConcatPrompts: boolean; + initialImage: string | null; size: { width: ParameterWidth; height: ParameterHeight; @@ -109,13 +110,12 @@ export const initialRegionalPromptsState: RegionalPromptsState = { layers: [], globalMaskLayerOpacity: 0.5, // this globally changes all mask layers' opacity isEnabled: true, - baseLayer: { - positivePrompt: '', - negativePrompt: '', - positivePrompt2: '', - negativePrompt2: '', - shouldConcatPrompts: true, - }, + positivePrompt: '', + negativePrompt: '', + positivePrompt2: '', + negativePrompt2: '', + shouldConcatPrompts: true, + initialImage: null, size: { width: 512, height: 512, @@ -124,10 +124,13 @@ export const initialRegionalPromptsState: RegionalPromptsState = { }; const isLine = (obj: VectorMaskLine | VectorMaskRect): obj is VectorMaskLine => obj.type === 'vector_mask_line'; -export const isVectorMaskLayer = (layer?: Layer): layer is VectorMaskLayer => layer?.type === 'vector_mask_layer'; +export const isMaskedGuidanceLayer = (layer?: Layer): layer is MaskedGuidanceLayer => + layer?.type === 'masked_guidance_layer'; +export const isRenderableLayer = (layer?: Layer): layer is MaskedGuidanceLayer => + layer?.type === 'masked_guidance_layer' || layer?.type === 'controlnet_layer'; const resetLayer = (layer: Layer) => { - if (layer.type === 'vector_mask_layer') { - layer.objects = []; + if (layer.type === 'masked_guidance_layer') { + layer.maskObjects = []; layer.bbox = null; layer.isVisible = true; layer.needsPixelBbox = false; @@ -135,12 +138,12 @@ const resetLayer = (layer: Layer) => { return; } - if (layer.type === 'control_layer') { + if (layer.type === 'controlnet_layer') { // TODO } }; const getVectorMaskPreviewColor = (state: RegionalPromptsState): RgbColor => { - const vmLayers = state.layers.filter(isVectorMaskLayer); + const vmLayers = state.layers.filter(isMaskedGuidanceLayer); const lastColor = vmLayers[vmLayers.length - 1]?.previewColor; return LayerColors.next(lastColor); }; @@ -153,14 +156,14 @@ export const regionalPromptsSlice = createSlice({ layerAdded: { reducer: (state, action: PayloadAction) => { const type = action.payload; - if (type === 'vector_mask_layer') { - const layer: VectorMaskLayer = { - id: getVectorMaskLayerId(action.meta.uuid), - type, + if (type === 'masked_guidance_layer') { + const layer: MaskedGuidanceLayer = { + id: getMaskedGuidanceLayerId(action.meta.uuid), + type: 'masked_guidance_layer', isVisible: true, bbox: null, bboxNeedsUpdate: false, - objects: [], + maskObjects: [], previewColor: getVectorMaskPreviewColor(state), x: 0, y: 0, @@ -175,8 +178,19 @@ export const regionalPromptsSlice = createSlice({ return; } - if (type === 'control_layer') { - // TODO + if (type === 'controlnet_layer') { + const layer: ControlAdapterLayer = { + id: getControlLayerId(action.meta.uuid), + type: 'controlnet_layer', + controlAdapterId: action.meta.uuid, + x: 0, + y: 0, + bbox: null, + bboxNeedsUpdate: false, + isVisible: true, + }; + state.layers.push(layer); + state.selectedLayerId = layer.id; return; } }, @@ -197,7 +211,7 @@ export const regionalPromptsSlice = createSlice({ layerTranslated: (state, action: PayloadAction<{ layerId: string; x: number; y: number }>) => { const { layerId, x, y } = action.payload; const layer = state.layers.find((l) => l.id === layerId); - if (layer) { + if (isRenderableLayer(layer)) { layer.x = x; layer.y = y; } @@ -205,7 +219,7 @@ export const regionalPromptsSlice = createSlice({ layerBboxChanged: (state, action: PayloadAction<{ layerId: string; bbox: IRect | null }>) => { const { layerId, bbox } = action.payload; const layer = state.layers.find((l) => l.id === layerId); - if (layer) { + if (isRenderableLayer(layer)) { layer.bbox = bbox; layer.bboxNeedsUpdate = false; } @@ -258,21 +272,21 @@ export const regionalPromptsSlice = createSlice({ maskLayerPositivePromptChanged: (state, action: PayloadAction<{ layerId: string; prompt: string | null }>) => { const { layerId, prompt } = action.payload; const layer = state.layers.find((l) => l.id === layerId); - if (layer?.type === 'vector_mask_layer') { + if (layer?.type === 'masked_guidance_layer') { layer.positivePrompt = prompt; } }, maskLayerNegativePromptChanged: (state, action: PayloadAction<{ layerId: string; prompt: string | null }>) => { const { layerId, prompt } = action.payload; const layer = state.layers.find((l) => l.id === layerId); - if (layer?.type === 'vector_mask_layer') { + if (layer?.type === 'masked_guidance_layer') { layer.negativePrompt = prompt; } }, maskLayerIPAdapterAdded: { reducer: (state, action: PayloadAction) => { const layer = state.layers.find((l) => l.id === action.payload); - if (layer?.type === 'vector_mask_layer') { + if (layer?.type === 'masked_guidance_layer') { layer.ipAdapterIds.push(action.meta.uuid); } }, @@ -281,7 +295,7 @@ export const regionalPromptsSlice = createSlice({ maskLayerPreviewColorChanged: (state, action: PayloadAction<{ layerId: string; color: RgbColor }>) => { const { layerId, color } = action.payload; const layer = state.layers.find((l) => l.id === layerId); - if (layer?.type === 'vector_mask_layer') { + if (layer?.type === 'masked_guidance_layer') { layer.previewColor = color; } }, @@ -296,9 +310,9 @@ export const regionalPromptsSlice = createSlice({ ) => { const { layerId, points, tool } = action.payload; const layer = state.layers.find((l) => l.id === layerId); - if (layer?.type === 'vector_mask_layer') { - const lineId = getVectorMaskLayerLineId(layer.id, action.meta.uuid); - layer.objects.push({ + if (layer?.type === 'masked_guidance_layer') { + const lineId = getMaskedGuidanceLayerLineId(layer.id, action.meta.uuid); + layer.maskObjects.push({ type: 'vector_mask_line', tool: tool, id: lineId, @@ -321,8 +335,8 @@ export const regionalPromptsSlice = createSlice({ maskLayerPointsAdded: (state, action: PayloadAction<{ layerId: string; point: [number, number] }>) => { const { layerId, point } = action.payload; const layer = state.layers.find((l) => l.id === layerId); - if (layer?.type === 'vector_mask_layer') { - const lastLine = layer.objects.findLast(isLine); + if (layer?.type === 'masked_guidance_layer') { + const lastLine = layer.maskObjects.findLast(isLine); if (!lastLine) { return; } @@ -340,9 +354,9 @@ export const regionalPromptsSlice = createSlice({ return; } const layer = state.layers.find((l) => l.id === layerId); - if (layer?.type === 'vector_mask_layer') { - const id = getVectorMaskLayerRectId(layer.id, action.meta.uuid); - layer.objects.push({ + if (layer?.type === 'masked_guidance_layer') { + const id = getMaskedGuidnaceLayerRectId(layer.id, action.meta.uuid); + layer.maskObjects.push({ type: 'vector_mask_rect', id, x: rect.x - layer.x, @@ -361,7 +375,7 @@ export const regionalPromptsSlice = createSlice({ ) => { const { layerId, autoNegative } = action.payload; const layer = state.layers.find((l) => l.id === layerId); - if (layer?.type === 'vector_mask_layer') { + if (layer?.type === 'masked_guidance_layer') { layer.autoNegative = autoNegative; } }, @@ -369,19 +383,19 @@ export const regionalPromptsSlice = createSlice({ //#region Base Layer positivePromptChanged: (state, action: PayloadAction) => { - state.baseLayer.positivePrompt = action.payload; + state.positivePrompt = action.payload; }, negativePromptChanged: (state, action: PayloadAction) => { - state.baseLayer.negativePrompt = action.payload; + state.negativePrompt = action.payload; }, positivePrompt2Changed: (state, action: PayloadAction) => { - state.baseLayer.positivePrompt2 = action.payload; + state.positivePrompt2 = action.payload; }, negativePrompt2Changed: (state, action: PayloadAction) => { - state.baseLayer.negativePrompt2 = action.payload; + state.negativePrompt2 = action.payload; }, shouldConcatPromptsChanged: (state, action: PayloadAction) => { - state.baseLayer.shouldConcatPrompts = action.payload; + state.shouldConcatPrompts = action.payload; }, widthChanged: (state, action: PayloadAction<{ width: number; updateAspectRatio?: boolean }>) => { const { width, updateAspectRatio } = action.payload; @@ -418,13 +432,13 @@ export const regionalPromptsSlice = createSlice({ }, undo: (state) => { // Invalidate the bbox for all layers to prevent stale bboxes - for (const layer of state.layers) { + for (const layer of state.layers.filter(isRenderableLayer)) { layer.bboxNeedsUpdate = true; } }, redo: (state) => { // Invalidate the bbox for all layers to prevent stale bboxes - for (const layer of state.layers) { + for (const layer of state.layers.filter(isRenderableLayer)) { layer.bboxNeedsUpdate = true; } }, @@ -432,7 +446,7 @@ export const regionalPromptsSlice = createSlice({ }, extraReducers(builder) { builder.addCase(controlAdapterRemoved, (state, action) => { - state.layers.filter(isVectorMaskLayer).forEach((layer) => { + state.layers.filter(isMaskedGuidanceLayer).forEach((layer) => { layer.ipAdapterIds = layer.ipAdapterIds.filter((id) => id !== action.payload.id); }); }); @@ -559,19 +573,20 @@ export const BACKGROUND_LAYER_ID = 'background_layer'; export const BACKGROUND_RECT_ID = 'background_layer.rect'; // Names (aka classes) for Konva layers and objects -export const VECTOR_MASK_LAYER_NAME = 'vector_mask_layer'; -export const VECTOR_MASK_LAYER_LINE_NAME = 'vector_mask_layer.line'; -export const VECTOR_MASK_LAYER_OBJECT_GROUP_NAME = 'vector_mask_layer.object_group'; -export const VECTOR_MASK_LAYER_RECT_NAME = 'vector_mask_layer.rect'; +export const MASKED_GUIDANCE_LAYER_NAME = 'masked_guidance_layer'; +export const MASKED_GUIDANCE_LAYER_LINE_NAME = 'masked_guidance_layer.line'; +export const MASKED_GUIDANCE_LAYER_OBJECT_GROUP_NAME = 'masked_guidance_layer.object_group'; +export const MASKED_GUIDANCE_LAYER_RECT_NAME = 'masked_guidance_layer.rect'; export const LAYER_BBOX_NAME = 'layer.bbox'; // Getters for non-singleton layer and object IDs -const getVectorMaskLayerId = (layerId: string) => `${VECTOR_MASK_LAYER_NAME}_${layerId}`; -const getVectorMaskLayerLineId = (layerId: string, lineId: string) => `${layerId}.line_${lineId}`; -const getVectorMaskLayerRectId = (layerId: string, lineId: string) => `${layerId}.rect_${lineId}`; -export const getVectorMaskLayerObjectGroupId = (layerId: string, groupId: string) => +const getMaskedGuidanceLayerId = (layerId: string) => `${MASKED_GUIDANCE_LAYER_NAME}_${layerId}`; +const getMaskedGuidanceLayerLineId = (layerId: string, lineId: string) => `${layerId}.line_${lineId}`; +const getMaskedGuidnaceLayerRectId = (layerId: string, lineId: string) => `${layerId}.rect_${lineId}`; +export const getMaskedGuidanceLayerObjectGroupId = (layerId: string, groupId: string) => `${layerId}.objectGroup_${groupId}`; export const getLayerBboxId = (layerId: string) => `${layerId}.bbox`; +const getControlLayerId = (layerId: string) => `control_layer_${layerId}`; export const regionalPromptsPersistConfig: PersistConfig = { name: regionalPromptsSlice.name, diff --git a/invokeai/frontend/web/src/features/regionalPrompts/util/bbox.ts b/invokeai/frontend/web/src/features/regionalPrompts/util/bbox.ts index 3182cbca7e..b17d805552 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/util/bbox.ts +++ b/invokeai/frontend/web/src/features/regionalPrompts/util/bbox.ts @@ -1,6 +1,6 @@ import openBase64ImageInTab from 'common/util/openBase64ImageInTab'; import { imageDataToDataURL } from 'features/canvas/util/blobToDataURL'; -import { VECTOR_MASK_LAYER_OBJECT_GROUP_NAME } from 'features/regionalPrompts/store/regionalPromptsSlice'; +import { MASKED_GUIDANCE_LAYER_OBJECT_GROUP_NAME } from 'features/regionalPrompts/store/regionalPromptsSlice'; import Konva from 'konva'; import type { Layer as KonvaLayerType } from 'konva/lib/Layer'; import type { IRect } from 'konva/lib/types'; @@ -81,7 +81,7 @@ export const getLayerBboxPixels = (layer: KonvaLayerType, preview: boolean = fal offscreenStage.add(layerClone); for (const child of layerClone.getChildren()) { - if (child.name() === VECTOR_MASK_LAYER_OBJECT_GROUP_NAME) { + if (child.name() === MASKED_GUIDANCE_LAYER_OBJECT_GROUP_NAME) { // We need to cache the group to ensure it composites out eraser strokes correctly child.opacity(1); child.cache(); diff --git a/invokeai/frontend/web/src/features/regionalPrompts/util/getLayerBlobs.ts b/invokeai/frontend/web/src/features/regionalPrompts/util/getLayerBlobs.ts index da8108784d..723d2611fd 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/util/getLayerBlobs.ts +++ b/invokeai/frontend/web/src/features/regionalPrompts/util/getLayerBlobs.ts @@ -1,7 +1,7 @@ import { getStore } from 'app/store/nanostores/store'; import openBase64ImageInTab from 'common/util/openBase64ImageInTab'; import { blobToDataURL } from 'features/canvas/util/blobToDataURL'; -import { isVectorMaskLayer, VECTOR_MASK_LAYER_NAME } from 'features/regionalPrompts/store/regionalPromptsSlice'; +import { isMaskedGuidanceLayer, MASKED_GUIDANCE_LAYER_NAME } from 'features/regionalPrompts/store/regionalPromptsSlice'; import { renderers } from 'features/regionalPrompts/util/renderers'; import Konva from 'konva'; import { assert } from 'tsafe'; @@ -19,12 +19,12 @@ export const getRegionalPromptLayerBlobs = async ( const state = getStore().getState(); const { layers } = state.regionalPrompts.present; const { width, height } = state.regionalPrompts.present.size; - const reduxLayers = layers.filter(isVectorMaskLayer); + const reduxLayers = layers.filter(isMaskedGuidanceLayer); const container = document.createElement('div'); const stage = new Konva.Stage({ container, width, height }); renderers.renderLayers(stage, reduxLayers, 1, 'brush'); - const konvaLayers = stage.find(`.${VECTOR_MASK_LAYER_NAME}`); + const konvaLayers = stage.find(`.${MASKED_GUIDANCE_LAYER_NAME}`); const blobs: Record = {}; // First remove all layers diff --git a/invokeai/frontend/web/src/features/regionalPrompts/util/renderers.ts b/invokeai/frontend/web/src/features/regionalPrompts/util/renderers.ts index 4f008c7758..903482b362 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/util/renderers.ts +++ b/invokeai/frontend/web/src/features/regionalPrompts/util/renderers.ts @@ -3,8 +3,8 @@ import { rgbaColorToString, rgbColorToString } from 'features/canvas/util/colorT import { getScaledFlooredCursorPosition } from 'features/regionalPrompts/hooks/mouseEventHooks'; import type { Layer, + MaskedGuidanceLayer, Tool, - VectorMaskLayer, VectorMaskLine, VectorMaskRect, } from 'features/regionalPrompts/store/regionalPromptsSlice'; @@ -13,19 +13,19 @@ import { BACKGROUND_LAYER_ID, BACKGROUND_RECT_ID, getLayerBboxId, - getVectorMaskLayerObjectGroupId, - isVectorMaskLayer, + getMaskedGuidanceLayerObjectGroupId, + isMaskedGuidanceLayer, LAYER_BBOX_NAME, + MASKED_GUIDANCE_LAYER_LINE_NAME, + MASKED_GUIDANCE_LAYER_NAME, + MASKED_GUIDANCE_LAYER_OBJECT_GROUP_NAME, + MASKED_GUIDANCE_LAYER_RECT_NAME, TOOL_PREVIEW_BRUSH_BORDER_INNER_ID, TOOL_PREVIEW_BRUSH_BORDER_OUTER_ID, TOOL_PREVIEW_BRUSH_FILL_ID, TOOL_PREVIEW_BRUSH_GROUP_ID, TOOL_PREVIEW_LAYER_ID, TOOL_PREVIEW_RECT_ID, - VECTOR_MASK_LAYER_LINE_NAME, - VECTOR_MASK_LAYER_NAME, - VECTOR_MASK_LAYER_OBJECT_GROUP_NAME, - VECTOR_MASK_LAYER_RECT_NAME, } from 'features/regionalPrompts/store/regionalPromptsSlice'; import { getLayerBboxFast, getLayerBboxPixels } from 'features/regionalPrompts/util/bbox'; import Konva from 'konva'; @@ -54,7 +54,7 @@ const getIsSelected = (layerId?: string | null) => { }; const selectVectorMaskObjects = (node: Konva.Node) => { - return node.name() === VECTOR_MASK_LAYER_LINE_NAME || node.name() === VECTOR_MASK_LAYER_RECT_NAME; + return node.name() === MASKED_GUIDANCE_LAYER_LINE_NAME || node.name() === MASKED_GUIDANCE_LAYER_RECT_NAME; }; /** @@ -138,7 +138,7 @@ const renderToolPreview = ( isMouseOver: boolean, brushSize: number ) => { - const layerCount = stage.find(`.${VECTOR_MASK_LAYER_NAME}`).length; + const layerCount = stage.find(`.${MASKED_GUIDANCE_LAYER_NAME}`).length; // Update the stage's pointer style if (layerCount === 0) { // We have no layers, so we should not render any tool @@ -221,13 +221,13 @@ const renderToolPreview = ( */ const createVectorMaskLayer = ( stage: Konva.Stage, - reduxLayer: VectorMaskLayer, + reduxLayer: MaskedGuidanceLayer, onLayerPosChanged?: (layerId: string, x: number, y: number) => void ) => { // This layer hasn't been added to the konva state yet const konvaLayer = new Konva.Layer({ id: reduxLayer.id, - name: VECTOR_MASK_LAYER_NAME, + name: MASKED_GUIDANCE_LAYER_NAME, draggable: true, dragDistance: 0, }); @@ -259,8 +259,8 @@ const createVectorMaskLayer = ( // The object group holds all of the layer's objects (e.g. lines and rects) const konvaObjectGroup = new Konva.Group({ - id: getVectorMaskLayerObjectGroupId(reduxLayer.id, uuidv4()), - name: VECTOR_MASK_LAYER_OBJECT_GROUP_NAME, + id: getMaskedGuidanceLayerObjectGroupId(reduxLayer.id, uuidv4()), + name: MASKED_GUIDANCE_LAYER_OBJECT_GROUP_NAME, listening: false, }); konvaLayer.add(konvaObjectGroup); @@ -279,7 +279,7 @@ const createVectorMaskLine = (reduxObject: VectorMaskLine, konvaGroup: Konva.Gro const vectorMaskLine = new Konva.Line({ id: reduxObject.id, key: reduxObject.id, - name: VECTOR_MASK_LAYER_LINE_NAME, + name: MASKED_GUIDANCE_LAYER_LINE_NAME, strokeWidth: reduxObject.strokeWidth, tension: 0, lineCap: 'round', @@ -301,7 +301,7 @@ const createVectorMaskRect = (reduxObject: VectorMaskRect, konvaGroup: Konva.Gro const vectorMaskRect = new Konva.Rect({ id: reduxObject.id, key: reduxObject.id, - name: VECTOR_MASK_LAYER_RECT_NAME, + name: MASKED_GUIDANCE_LAYER_RECT_NAME, x: reduxObject.x, y: reduxObject.y, width: reduxObject.width, @@ -322,7 +322,7 @@ const createVectorMaskRect = (reduxObject: VectorMaskRect, konvaGroup: Konva.Gro */ const renderVectorMaskLayer = ( stage: Konva.Stage, - reduxLayer: VectorMaskLayer, + reduxLayer: MaskedGuidanceLayer, globalMaskLayerOpacity: number, tool: Tool, onLayerPosChanged?: (layerId: string, x: number, y: number) => void @@ -340,13 +340,13 @@ const renderVectorMaskLayer = ( // Convert the color to a string, stripping the alpha - the object group will handle opacity. const rgbColor = rgbColorToString(reduxLayer.previewColor); - const konvaObjectGroup = konvaLayer.findOne(`.${VECTOR_MASK_LAYER_OBJECT_GROUP_NAME}`); + const konvaObjectGroup = konvaLayer.findOne(`.${MASKED_GUIDANCE_LAYER_OBJECT_GROUP_NAME}`); assert(konvaObjectGroup, `Object group not found for layer ${reduxLayer.id}`); // We use caching to handle "global" layer opacity, but caching is expensive and we should only do it when required. let groupNeedsCache = false; - const objectIds = reduxLayer.objects.map(mapId); + const objectIds = reduxLayer.maskObjects.map(mapId); for (const objectNode of konvaObjectGroup.find(selectVectorMaskObjects)) { if (!objectIds.includes(objectNode.id())) { objectNode.destroy(); @@ -354,7 +354,7 @@ const renderVectorMaskLayer = ( } } - for (const reduxObject of reduxLayer.objects) { + for (const reduxObject of reduxLayer.maskObjects) { if (reduxObject.type === 'vector_mask_line') { const vectorMaskLine = stage.findOne(`#${reduxObject.id}`) ?? createVectorMaskLine(reduxObject, konvaObjectGroup); @@ -419,14 +419,14 @@ const renderLayers = ( const reduxLayerIds = reduxLayers.map(mapId); // Remove un-rendered layers - for (const konvaLayer of stage.find(`.${VECTOR_MASK_LAYER_NAME}`)) { + for (const konvaLayer of stage.find(`.${MASKED_GUIDANCE_LAYER_NAME}`)) { if (!reduxLayerIds.includes(konvaLayer.id())) { konvaLayer.destroy(); } } for (const reduxLayer of reduxLayers) { - if (isVectorMaskLayer(reduxLayer)) { + if (isMaskedGuidanceLayer(reduxLayer)) { renderVectorMaskLayer(stage, reduxLayer, globalMaskLayerOpacity, tool, onLayerPosChanged); } } @@ -494,14 +494,14 @@ const renderBbox = ( } for (const reduxLayer of reduxLayers) { - if (reduxLayer.type === 'vector_mask_layer') { + if (reduxLayer.type === 'masked_guidance_layer') { const konvaLayer = stage.findOne(`#${reduxLayer.id}`); assert(konvaLayer, `Layer ${reduxLayer.id} not found in stage`); let bbox = reduxLayer.bbox; // We only need to recalculate the bbox if the layer has changed and it has objects - if (reduxLayer.bboxNeedsUpdate && reduxLayer.objects.length) { + if (reduxLayer.bboxNeedsUpdate && reduxLayer.maskObjects.length) { // We only need to use the pixel-perfect bounding box if the layer has eraser strokes bbox = reduxLayer.needsPixelBbox ? getLayerBboxPixels(konvaLayer) : getLayerBboxFast(konvaLayer); // Update the layer's bbox in the redux store diff --git a/invokeai/frontend/web/src/features/sdxl/components/SDXLPrompts/ParamSDXLNegativeStylePrompt.tsx b/invokeai/frontend/web/src/features/sdxl/components/SDXLPrompts/ParamSDXLNegativeStylePrompt.tsx index f5d817a2e2..51347c769d 100644 --- a/invokeai/frontend/web/src/features/sdxl/components/SDXLPrompts/ParamSDXLNegativeStylePrompt.tsx +++ b/invokeai/frontend/web/src/features/sdxl/components/SDXLPrompts/ParamSDXLNegativeStylePrompt.tsx @@ -11,7 +11,7 @@ import { useTranslation } from 'react-i18next'; export const ParamSDXLNegativeStylePrompt = memo(() => { const dispatch = useAppDispatch(); - const prompt = useAppSelector((s) => s.regionalPrompts.present.baseLayer.negativePrompt2); + const prompt = useAppSelector((s) => s.regionalPrompts.present.negativePrompt2); const textareaRef = useRef(null); const { t } = useTranslation(); const handleChange = useCallback( diff --git a/invokeai/frontend/web/src/features/sdxl/components/SDXLPrompts/ParamSDXLPositiveStylePrompt.tsx b/invokeai/frontend/web/src/features/sdxl/components/SDXLPrompts/ParamSDXLPositiveStylePrompt.tsx index 39ce4eea09..cdff420b47 100644 --- a/invokeai/frontend/web/src/features/sdxl/components/SDXLPrompts/ParamSDXLPositiveStylePrompt.tsx +++ b/invokeai/frontend/web/src/features/sdxl/components/SDXLPrompts/ParamSDXLPositiveStylePrompt.tsx @@ -10,7 +10,7 @@ import { useTranslation } from 'react-i18next'; export const ParamSDXLPositiveStylePrompt = memo(() => { const dispatch = useAppDispatch(); - const prompt = useAppSelector((s) => s.regionalPrompts.present.baseLayer.positivePrompt2); + const prompt = useAppSelector((s) => s.regionalPrompts.present.positivePrompt2); const textareaRef = useRef(null); const { t } = useTranslation(); const handleChange = useCallback( diff --git a/invokeai/frontend/web/src/features/sdxl/components/SDXLPrompts/SDXLConcatButton.tsx b/invokeai/frontend/web/src/features/sdxl/components/SDXLPrompts/SDXLConcatButton.tsx index 01e859825d..b25698cc1d 100644 --- a/invokeai/frontend/web/src/features/sdxl/components/SDXLPrompts/SDXLConcatButton.tsx +++ b/invokeai/frontend/web/src/features/sdxl/components/SDXLPrompts/SDXLConcatButton.tsx @@ -6,7 +6,7 @@ import { useTranslation } from 'react-i18next'; import { PiLinkSimpleBold, PiLinkSimpleBreakBold } from 'react-icons/pi'; export const SDXLConcatButton = memo(() => { - const shouldConcatPrompts = useAppSelector((s) => s.regionalPrompts.present.baseLayer.shouldConcatPrompts); + const shouldConcatPrompts = useAppSelector((s) => s.regionalPrompts.present.shouldConcatPrompts); const dispatch = useAppDispatch(); const { t } = useTranslation(); diff --git a/invokeai/frontend/web/src/features/sdxl/components/SDXLPrompts/SDXLPrompts.tsx b/invokeai/frontend/web/src/features/sdxl/components/SDXLPrompts/SDXLPrompts.tsx index 4f47ecdda4..edfd6302a4 100644 --- a/invokeai/frontend/web/src/features/sdxl/components/SDXLPrompts/SDXLPrompts.tsx +++ b/invokeai/frontend/web/src/features/sdxl/components/SDXLPrompts/SDXLPrompts.tsx @@ -8,7 +8,7 @@ import { ParamSDXLNegativeStylePrompt } from './ParamSDXLNegativeStylePrompt'; import { ParamSDXLPositiveStylePrompt } from './ParamSDXLPositiveStylePrompt'; export const SDXLPrompts = memo(() => { - const shouldConcatPrompts = useAppSelector((s) => s.regionalPrompts.present.baseLayer.shouldConcatPrompts); + const shouldConcatPrompts = useAppSelector((s) => s.regionalPrompts.present.shouldConcatPrompts); return ( diff --git a/invokeai/frontend/web/src/features/settingsAccordions/components/ControlSettingsAccordion/ControlSettingsAccordion.tsx b/invokeai/frontend/web/src/features/settingsAccordions/components/ControlSettingsAccordion/ControlSettingsAccordion.tsx index 449091536e..843ed3a4b7 100644 --- a/invokeai/frontend/web/src/features/settingsAccordions/components/ControlSettingsAccordion/ControlSettingsAccordion.tsx +++ b/invokeai/frontend/web/src/features/settingsAccordions/components/ControlSettingsAccordion/ControlSettingsAccordion.tsx @@ -13,7 +13,7 @@ import { selectValidIPAdapters, selectValidT2IAdapters, } from 'features/controlAdapters/store/controlAdaptersSlice'; -import { isVectorMaskLayer, selectRegionalPromptsSlice } from 'features/regionalPrompts/store/regionalPromptsSlice'; +import { isMaskedGuidanceLayer, selectRegionalPromptsSlice } from 'features/regionalPrompts/store/regionalPromptsSlice'; import { useStandaloneAccordionToggle } from 'features/settingsAccordions/hooks/useStandaloneAccordionToggle'; import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus'; import { Fragment, memo } from 'react'; @@ -28,7 +28,7 @@ const selector = createMemoizedSelector( const enabledNonRegionalIPAdapterCount = selectAllIPAdapters(controlAdapters) .filter( - (ca) => !regionalPrompts.present.layers.filter(isVectorMaskLayer).some((l) => l.ipAdapterIds.includes(ca.id)) + (ca) => !regionalPrompts.present.layers.filter(isMaskedGuidanceLayer).some((l) => l.ipAdapterIds.includes(ca.id)) ) .filter((ca) => ca.isEnabled).length; @@ -59,7 +59,7 @@ const selector = createMemoizedSelector( } const controlAdapterIds = selectControlAdapterIds(controlAdapters).filter( - (id) => !regionalPrompts.present.layers.filter(isVectorMaskLayer).some((l) => l.ipAdapterIds.includes(id)) + (id) => !regionalPrompts.present.layers.filter(isMaskedGuidanceLayer).some((l) => l.ipAdapterIds.includes(id)) ); return {