feat(ui): revise internal state for RCC

This commit is contained in:
psychedelicious 2024-04-26 16:56:07 +10:00 committed by Kent Keirsey
parent 2f6fec8c6c
commit 6f5f3381f9
42 changed files with 170 additions and 155 deletions

View File

@ -28,7 +28,7 @@ export const addDynamicPromptsListener = (startAppListening: AppStartListening)
effect: async (action, { dispatch, getState, cancelActiveListeners, delay }) => { effect: async (action, { dispatch, getState, cancelActiveListeners, delay }) => {
cancelActiveListeners(); cancelActiveListeners();
const state = getState(); const state = getState();
const { positivePrompt } = state.regionalPrompts.present.baseLayer; const { positivePrompt } = state.regionalPrompts.present;
const { maxPrompts } = state.dynamicPrompts; const { maxPrompts } = state.dynamicPrompts;
if (state.config.disabledFeatures.includes('dynamicPrompting')) { if (state.config.disabledFeatures.includes('dynamicPrompting')) {

View File

@ -29,7 +29,7 @@ const selector = createMemoizedSelector(
], ],
(controlAdapters, generation, system, nodes, dynamicPrompts, regionalPrompts, activeTabName) => { (controlAdapters, generation, system, nodes, dynamicPrompts, regionalPrompts, activeTabName) => {
const { initialImage, model } = generation; const { initialImage, model } = generation;
const { positivePrompt } = regionalPrompts.present.baseLayer; const { positivePrompt } = regionalPrompts.present;
const { isConnected } = system; const { isConnected } = system;

View File

@ -2,7 +2,7 @@ import type { RootState } from 'app/store/store';
import { selectValidIPAdapters } from 'features/controlAdapters/store/controlAdaptersSlice'; import { selectValidIPAdapters } from 'features/controlAdapters/store/controlAdaptersSlice';
import type { IPAdapterConfig } from 'features/controlAdapters/store/types'; import type { IPAdapterConfig } from 'features/controlAdapters/store/types';
import type { ImageField } from 'features/nodes/types/common'; 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 { differenceBy } from 'lodash-es';
import type { import type {
CollectInvocation, CollectInvocation,
@ -29,7 +29,7 @@ export const addIPAdapterToLinearGraph = async (
}); });
const regionalIPAdapterIds = state.regionalPrompts.present.layers const regionalIPAdapterIds = state.regionalPrompts.present.layers
.filter(isVectorMaskLayer) .filter(isMaskedGuidanceLayer)
.map((l) => l.ipAdapterIds) .map((l) => l.ipAdapterIds)
.flat(); .flat();

View File

@ -13,7 +13,7 @@ import {
PROMPT_REGION_POSITIVE_COND_INVERTED_PREFIX, PROMPT_REGION_POSITIVE_COND_INVERTED_PREFIX,
PROMPT_REGION_POSITIVE_COND_PREFIX, PROMPT_REGION_POSITIVE_COND_PREFIX,
} from 'features/nodes/util/graph/constants'; } 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 { getRegionalPromptLayerBlobs } from 'features/regionalPrompts/util/getLayerBlobs';
import { size } from 'lodash-es'; import { size } from 'lodash-es';
import { imagesApi } from 'services/api/endpoints/images'; 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 const layers = state.regionalPrompts.present.layers
// Only support vector mask layers now // Only support vector mask layers now
// TODO: Image masks // TODO: Image masks
.filter(isVectorMaskLayer) .filter(isMaskedGuidanceLayer)
// Only visible layers are rendered on the canvas // Only visible layers are rendered on the canvas
.filter((l) => l.isVisible) .filter((l) => l.isVisible)
// Only layers with prompts get added to the graph // Only layers with prompts get added to the graph

View File

@ -55,7 +55,7 @@ export const buildCanvasImageToImageGraph = async (
seamlessXAxis, seamlessXAxis,
seamlessYAxis, seamlessYAxis,
} = state.generation; } = 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 // The bounding box determines width and height, not the width and height params
const { width, height } = state.canvas.boundingBoxDimensions; const { width, height } = state.canvas.boundingBoxDimensions;

View File

@ -64,7 +64,7 @@ export const buildCanvasInpaintGraph = async (
canvasCoherenceEdgeSize, canvasCoherenceEdgeSize,
maskBlur, maskBlur,
} = state.generation; } = state.generation;
const { positivePrompt, negativePrompt } = state.regionalPrompts.present.baseLayer; const { positivePrompt, negativePrompt } = state.regionalPrompts.present;
if (!model) { if (!model) {
log.error('No model found in state'); log.error('No model found in state');

View File

@ -76,7 +76,7 @@ export const buildCanvasOutpaintGraph = async (
canvasCoherenceEdgeSize, canvasCoherenceEdgeSize,
maskBlur, maskBlur,
} = state.generation; } = state.generation;
const { positivePrompt, negativePrompt } = state.regionalPrompts.present.baseLayer; const { positivePrompt, negativePrompt } = state.regionalPrompts.present;
if (!model) { if (!model) {
log.error('No model found in state'); log.error('No model found in state');

View File

@ -55,7 +55,7 @@ export const buildCanvasSDXLImageToImageGraph = async (
seamlessYAxis, seamlessYAxis,
img2imgStrength: strength, img2imgStrength: strength,
} = state.generation; } = state.generation;
const { positivePrompt, negativePrompt } = state.regionalPrompts.present.baseLayer; const { positivePrompt, negativePrompt } = state.regionalPrompts.present;
const { refinerModel, refinerStart } = state.sdxl; const { refinerModel, refinerStart } = state.sdxl;

View File

@ -64,7 +64,7 @@ export const buildCanvasSDXLInpaintGraph = async (
canvasCoherenceEdgeSize, canvasCoherenceEdgeSize,
maskBlur, maskBlur,
} = state.generation; } = state.generation;
const { positivePrompt, negativePrompt } = state.regionalPrompts.present.baseLayer; const { positivePrompt, negativePrompt } = state.regionalPrompts.present;
const { refinerModel, refinerStart } = state.sdxl; const { refinerModel, refinerStart } = state.sdxl;

View File

@ -76,7 +76,7 @@ export const buildCanvasSDXLOutpaintGraph = async (
canvasCoherenceEdgeSize, canvasCoherenceEdgeSize,
maskBlur, maskBlur,
} = state.generation; } = state.generation;
const { positivePrompt, negativePrompt } = state.regionalPrompts.present.baseLayer; const { positivePrompt, negativePrompt } = state.regionalPrompts.present;
const { refinerModel, refinerStart } = state.sdxl; const { refinerModel, refinerStart } = state.sdxl;

View File

@ -44,7 +44,7 @@ export const buildCanvasSDXLTextToImageGraph = async (state: RootState): Promise
seamlessXAxis, seamlessXAxis,
seamlessYAxis, seamlessYAxis,
} = state.generation; } = 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 // The bounding box determines width and height, not the width and height params
const { width, height } = state.canvas.boundingBoxDimensions; const { width, height } = state.canvas.boundingBoxDimensions;

View File

@ -44,7 +44,7 @@ export const buildCanvasTextToImageGraph = async (state: RootState): Promise<Non
seamlessXAxis, seamlessXAxis,
seamlessYAxis, seamlessYAxis,
} = state.generation; } = 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 // The bounding box determines width and height, not the width and height params
const { width, height } = state.canvas.boundingBoxDimensions; const { width, height } = state.canvas.boundingBoxDimensions;

View File

@ -10,7 +10,7 @@ import { getHasMetadata, removeMetadata } from './metadata';
export const prepareLinearUIBatch = (state: RootState, graph: NonNullableGraph, prepend: boolean): BatchConfig => { export const prepareLinearUIBatch = (state: RootState, graph: NonNullableGraph, prepend: boolean): BatchConfig => {
const { iterations, model, shouldRandomizeSeed, seed } = state.generation; const { iterations, model, shouldRandomizeSeed, seed } = state.generation;
const { shouldConcatPrompts } = state.regionalPrompts.present.baseLayer; const { shouldConcatPrompts } = state.regionalPrompts.present;
const { prompts, seedBehaviour } = state.dynamicPrompts; const { prompts, seedBehaviour } = state.dynamicPrompts;
const data: Batch['data'] = []; const data: Batch['data'] = [];

View File

@ -53,7 +53,7 @@ export const buildLinearImageToImageGraph = async (state: RootState): Promise<No
seamlessXAxis, seamlessXAxis,
seamlessYAxis, seamlessYAxis,
} = state.generation; } = state.generation;
const { positivePrompt, negativePrompt } = state.regionalPrompts.present.baseLayer; const { positivePrompt, negativePrompt } = state.regionalPrompts.present;
const { width, height } = state.regionalPrompts.present.size; const { width, height } = state.regionalPrompts.present.size;
/** /**

View File

@ -53,7 +53,7 @@ export const buildLinearSDXLImageToImageGraph = async (state: RootState): Promis
seamlessYAxis, seamlessYAxis,
img2imgStrength: strength, img2imgStrength: strength,
} = state.generation; } = state.generation;
const { positivePrompt, negativePrompt } = state.regionalPrompts.present.baseLayer; const { positivePrompt, negativePrompt } = state.regionalPrompts.present;
const { width, height } = state.regionalPrompts.present.size; const { width, height } = state.regionalPrompts.present.size;
const { refinerModel, refinerStart } = state.sdxl; const { refinerModel, refinerStart } = state.sdxl;

View File

@ -41,7 +41,7 @@ export const buildLinearSDXLTextToImageGraph = async (state: RootState): Promise
seamlessXAxis, seamlessXAxis,
seamlessYAxis, seamlessYAxis,
} = state.generation; } = state.generation;
const { positivePrompt, negativePrompt } = state.regionalPrompts.present.baseLayer; const { positivePrompt, negativePrompt } = state.regionalPrompts.present;
const { width, height } = state.regionalPrompts.present.size; const { width, height } = state.regionalPrompts.present.size;
const { refinerModel, refinerStart } = state.sdxl; const { refinerModel, refinerStart } = state.sdxl;

View File

@ -42,7 +42,7 @@ export const buildLinearTextToImageGraph = async (state: RootState): Promise<Non
seamlessYAxis, seamlessYAxis,
seed, seed,
} = state.generation; } = state.generation;
const { positivePrompt, negativePrompt } = state.regionalPrompts.present.baseLayer; const { positivePrompt, negativePrompt } = state.regionalPrompts.present;
const { width, height } = state.regionalPrompts.present.size; const { width, height } = state.regionalPrompts.present.size;
const use_cpu = shouldUseCpuNoise; const use_cpu = shouldUseCpuNoise;

View File

@ -18,7 +18,7 @@ export const getBoardField = (state: RootState): BoardField | undefined => {
*/ */
export const getSDXLStylePrompts = (state: RootState): { positiveStylePrompt: string; negativeStylePrompt: string } => { export const getSDXLStylePrompts = (state: RootState): { positiveStylePrompt: string; negativeStylePrompt: string } => {
const { positivePrompt, negativePrompt, positivePrompt2, negativePrompt2, shouldConcatPrompts } = const { positivePrompt, negativePrompt, positivePrompt2, negativePrompt2, shouldConcatPrompts } =
state.regionalPrompts.present.baseLayer; state.regionalPrompts.present;
return { return {
positiveStylePrompt: shouldConcatPrompts ? positivePrompt : positivePrompt2, positiveStylePrompt: shouldConcatPrompts ? positivePrompt : positivePrompt2,

View File

@ -10,7 +10,7 @@ import { useTranslation } from 'react-i18next';
export const ParamNegativePrompt = memo(() => { export const ParamNegativePrompt = memo(() => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const prompt = useAppSelector((s) => s.regionalPrompts.present.baseLayer.negativePrompt); const prompt = useAppSelector((s) => s.regionalPrompts.present.negativePrompt);
const textareaRef = useRef<HTMLTextAreaElement>(null); const textareaRef = useRef<HTMLTextAreaElement>(null);
const { t } = useTranslation(); const { t } = useTranslation();
const _onChange = useCallback( const _onChange = useCallback(

View File

@ -14,7 +14,7 @@ import { useTranslation } from 'react-i18next';
export const ParamPositivePrompt = memo(() => { export const ParamPositivePrompt = memo(() => {
const dispatch = useAppDispatch(); 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 baseModel = useAppSelector((s) => s.generation.model)?.base;
const textareaRef = useRef<HTMLTextAreaElement>(null); const textareaRef = useRef<HTMLTextAreaElement>(null);

View File

@ -14,7 +14,7 @@ const selectPromptsCount = createSelector(
selectRegionalPromptsSlice, selectRegionalPromptsSlice,
selectDynamicPromptsSlice, selectDynamicPromptsSlice,
(regionalPrompts, dynamicPrompts) => (regionalPrompts, dynamicPrompts) =>
getShouldProcessPrompt(regionalPrompts.present.baseLayer.positivePrompt) ? dynamicPrompts.prompts.length : 1 getShouldProcessPrompt(regionalPrompts.present.positivePrompt) ? dynamicPrompts.prompts.length : 1
); );
type Props = { type Props = {

View File

@ -9,7 +9,7 @@ export const AddLayerButton = memo(() => {
const { t } = useTranslation(); const { t } = useTranslation();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const onClick = useCallback(() => { const onClick = useCallback(() => {
dispatch(layerAdded('vector_mask_layer')); dispatch(layerAdded('masked_guidance_layer'));
}, [dispatch]); }, [dispatch]);
return ( return (

View File

@ -2,7 +2,7 @@ import { Button, Flex } from '@invoke-ai/ui-library';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { import {
isVectorMaskLayer, isMaskedGuidanceLayer,
maskLayerIPAdapterAdded, maskLayerIPAdapterAdded,
maskLayerNegativePromptChanged, maskLayerNegativePromptChanged,
maskLayerPositivePromptChanged, maskLayerPositivePromptChanged,
@ -23,7 +23,7 @@ export const AddPromptButtons = ({ layerId }: AddPromptButtonProps) => {
() => () =>
createMemoizedSelector(selectRegionalPromptsSlice, (regionalPrompts) => { createMemoizedSelector(selectRegionalPromptsSlice, (regionalPrompts) => {
const layer = regionalPrompts.present.layers.find((l) => l.id === layerId); 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 { return {
canAddPositivePrompt: layer.positivePrompt === null, canAddPositivePrompt: layer.positivePrompt === null,
canAddNegativePrompt: layer.negativePrompt === null, canAddNegativePrompt: layer.negativePrompt === null,

View File

@ -2,7 +2,7 @@ import { Checkbox, FormControl, FormLabel } from '@invoke-ai/ui-library';
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { import {
isVectorMaskLayer, isMaskedGuidanceLayer,
maskLayerAutoNegativeChanged, maskLayerAutoNegativeChanged,
selectRegionalPromptsSlice, selectRegionalPromptsSlice,
} from 'features/regionalPrompts/store/regionalPromptsSlice'; } from 'features/regionalPrompts/store/regionalPromptsSlice';
@ -20,7 +20,7 @@ const useAutoNegative = (layerId: string) => {
() => () =>
createSelector(selectRegionalPromptsSlice, (regionalPrompts) => { createSelector(selectRegionalPromptsSlice, (regionalPrompts) => {
const layer = regionalPrompts.present.layers.find((l) => l.id === layerId); 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; return layer.autoNegative;
}), }),
[layerId] [layerId]

View File

@ -4,7 +4,7 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import RgbColorPicker from 'common/components/RgbColorPicker'; import RgbColorPicker from 'common/components/RgbColorPicker';
import { rgbColorToString } from 'features/canvas/util/colorToString'; import { rgbColorToString } from 'features/canvas/util/colorToString';
import { import {
isVectorMaskLayer, isMaskedGuidanceLayer,
maskLayerPreviewColorChanged, maskLayerPreviewColorChanged,
selectRegionalPromptsSlice, selectRegionalPromptsSlice,
} from 'features/regionalPrompts/store/regionalPromptsSlice'; } from 'features/regionalPrompts/store/regionalPromptsSlice';
@ -23,7 +23,7 @@ export const RPLayerColorPicker = memo(({ layerId }: Props) => {
() => () =>
createMemoizedSelector(selectRegionalPromptsSlice, (regionalPrompts) => { createMemoizedSelector(selectRegionalPromptsSlice, (regionalPrompts) => {
const layer = regionalPrompts.present.layers.find((l) => l.id === layerId); 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; return layer.previewColor;
}), }),
[layerId] [layerId]

View File

@ -2,7 +2,7 @@ import { Flex } from '@invoke-ai/ui-library';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import ControlAdapterConfig from 'features/controlAdapters/components/ControlAdapterConfig'; 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 { memo, useMemo } from 'react';
import { assert } from 'tsafe'; import { assert } from 'tsafe';
@ -14,7 +14,7 @@ export const RPLayerIPAdapterList = memo(({ layerId }: Props) => {
const selectIPAdapterIds = useMemo( const selectIPAdapterIds = useMemo(
() => () =>
createMemoizedSelector(selectRegionalPromptsSlice, (regionalPrompts) => { 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`); assert(layer, `Layer ${layerId} not found`);
return layer.ipAdapterIds; return layer.ipAdapterIds;
}), }),

View File

@ -11,7 +11,7 @@ import { RPLayerPositivePrompt } from 'features/regionalPrompts/components/RPLay
import RPLayerSettingsPopover from 'features/regionalPrompts/components/RPLayerSettingsPopover'; import RPLayerSettingsPopover from 'features/regionalPrompts/components/RPLayerSettingsPopover';
import { RPLayerVisibilityToggle } from 'features/regionalPrompts/components/RPLayerVisibilityToggle'; import { RPLayerVisibilityToggle } from 'features/regionalPrompts/components/RPLayerVisibilityToggle';
import { import {
isVectorMaskLayer, isMaskedGuidanceLayer,
layerSelected, layerSelected,
selectRegionalPromptsSlice, selectRegionalPromptsSlice,
} from 'features/regionalPrompts/store/regionalPromptsSlice'; } from 'features/regionalPrompts/store/regionalPromptsSlice';
@ -32,7 +32,7 @@ export const RPLayerListItem = memo(({ layerId }: Props) => {
() => () =>
createMemoizedSelector(selectRegionalPromptsSlice, (regionalPrompts) => { createMemoizedSelector(selectRegionalPromptsSlice, (regionalPrompts) => {
const layer = regionalPrompts.present.layers.find((l) => l.id === layerId); 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 { return {
color: rgbColorToString(layer.previewColor), color: rgbColorToString(layer.previewColor),
hasPositivePrompt: layer.positivePrompt !== null, hasPositivePrompt: layer.positivePrompt !== null,

View File

@ -2,7 +2,7 @@ import { IconButton, Menu, MenuButton, MenuDivider, MenuItem, MenuList } from '@
import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { import {
isVectorMaskLayer, isMaskedGuidanceLayer,
layerDeleted, layerDeleted,
layerMovedBackward, layerMovedBackward,
layerMovedForward, layerMovedForward,
@ -37,7 +37,7 @@ export const RPLayerMenu = memo(({ layerId }: Props) => {
() => () =>
createMemoizedSelector(selectRegionalPromptsSlice, (regionalPrompts) => { createMemoizedSelector(selectRegionalPromptsSlice, (regionalPrompts) => {
const layer = regionalPrompts.present.layers.find((l) => l.id === layerId); 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 layerIndex = regionalPrompts.present.layers.findIndex((l) => l.id === layerId);
const layerCount = regionalPrompts.present.layers.length; const layerCount = regionalPrompts.present.layers.length;
return { return {

View File

@ -6,12 +6,12 @@ import ScrollableContent from 'common/components/OverlayScrollbars/ScrollableCon
import { AddLayerButton } from 'features/regionalPrompts/components/AddLayerButton'; import { AddLayerButton } from 'features/regionalPrompts/components/AddLayerButton';
import { DeleteAllLayersButton } from 'features/regionalPrompts/components/DeleteAllLayersButton'; import { DeleteAllLayersButton } from 'features/regionalPrompts/components/DeleteAllLayersButton';
import { RPLayerListItem } from 'features/regionalPrompts/components/RPLayerListItem'; 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'; import { memo } from 'react';
const selectRPLayerIdsReversed = createMemoizedSelector(selectRegionalPromptsSlice, (regionalPrompts) => const selectRPLayerIdsReversed = createMemoizedSelector(selectRegionalPromptsSlice, (regionalPrompts) =>
regionalPrompts.present.layers regionalPrompts.present.layers
.filter(isVectorMaskLayer) .filter(isMaskedGuidanceLayer)
.map((l) => l.id) .map((l) => l.id)
.reverse() .reverse()
); );

View File

@ -9,7 +9,7 @@ import {
$isMouseOver, $isMouseOver,
$lastMouseDownPos, $lastMouseDownPos,
$tool, $tool,
isVectorMaskLayer, isMaskedGuidanceLayer,
layerBboxChanged, layerBboxChanged,
layerSelected, layerSelected,
layerTranslated, layerTranslated,
@ -32,7 +32,7 @@ const selectSelectedLayerColor = createMemoizedSelector(selectRegionalPromptsSli
if (!layer) { if (!layer) {
return null; 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; return layer.previewColor;
}); });

View File

@ -41,7 +41,7 @@ export const ToolChooser: React.FC = () => {
useHotkeys('shift+c', resetSelectedLayer); useHotkeys('shift+c', resetSelectedLayer);
const addLayer = useCallback(() => { const addLayer = useCallback(() => {
dispatch(layerAdded('vector_mask_layer')); dispatch(layerAdded('masked_guidance_layer'));
}, [dispatch]); }, [dispatch]);
useHotkeys('shift+a', addLayer); useHotkeys('shift+a', addLayer);

View File

@ -1,6 +1,6 @@
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/store/storeHooks'; 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 { useMemo } from 'react';
import { assert } from 'tsafe'; import { assert } from 'tsafe';
@ -9,7 +9,7 @@ export const useLayerPositivePrompt = (layerId: string) => {
() => () =>
createSelector(selectRegionalPromptsSlice, (regionalPrompts) => { createSelector(selectRegionalPromptsSlice, (regionalPrompts) => {
const layer = regionalPrompts.present.layers.find((l) => l.id === layerId); 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`); assert(layer.positivePrompt !== null, `Layer ${layerId} does not have a positive prompt`);
return layer.positivePrompt; return layer.positivePrompt;
}), }),
@ -24,7 +24,7 @@ export const useLayerNegativePrompt = (layerId: string) => {
() => () =>
createSelector(selectRegionalPromptsSlice, (regionalPrompts) => { createSelector(selectRegionalPromptsSlice, (regionalPrompts) => {
const layer = regionalPrompts.present.layers.find((l) => l.id === layerId); 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`); assert(layer.negativePrompt !== null, `Layer ${layerId} does not have a negative prompt`);
return layer.negativePrompt; return layer.negativePrompt;
}), }),
@ -39,7 +39,7 @@ export const useLayerIsVisible = (layerId: string) => {
() => () =>
createSelector(selectRegionalPromptsSlice, (regionalPrompts) => { createSelector(selectRegionalPromptsSlice, (regionalPrompts) => {
const layer = regionalPrompts.present.layers.find((l) => l.id === layerId); 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; return layer.isVisible;
}), }),
[layerId] [layerId]

View File

@ -1,6 +1,6 @@
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/store/storeHooks'; 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 { useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@ -9,7 +9,7 @@ const selectValidLayerCount = createSelector(selectRegionalPromptsSlice, (region
return 0; return 0;
} }
const validLayers = regionalPrompts.present.layers const validLayers = regionalPrompts.present.layers
.filter(isVectorMaskLayer) .filter(isMaskedGuidanceLayer)
.filter((l) => l.isVisible) .filter((l) => l.isVisible)
.filter((l) => { .filter((l) => {
const hasTextPrompt = Boolean(l.positivePrompt || l.negativePrompt); const hasTextPrompt = Boolean(l.positivePrompt || l.negativePrompt);

View File

@ -5,7 +5,6 @@ import { moveBackward, moveForward, moveToBack, moveToFront } from 'common/util/
import { deepClone } from 'common/util/deepClone'; import { deepClone } from 'common/util/deepClone';
import { roundToMultiple } from 'common/util/roundDownToMultiple'; import { roundToMultiple } from 'common/util/roundDownToMultiple';
import { controlAdapterRemoved, isAnyControlAdapterAdded } from 'features/controlAdapters/store/controlAdaptersSlice'; 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 { calculateNewSize } from 'features/parameters/components/ImageSize/calculateNewSize';
import { initialAspectRatioState } from 'features/parameters/components/ImageSize/constants'; import { initialAspectRatioState } from 'features/parameters/components/ImageSize/constants';
import type { AspectRatioState } from 'features/parameters/components/ImageSize/types'; import type { AspectRatioState } from 'features/parameters/components/ImageSize/types';
@ -51,41 +50,38 @@ export type VectorMaskRect = {
type LayerBase = { type LayerBase = {
id: string; id: string;
isVisible: boolean;
};
type RenderableLayerBase = LayerBase & {
x: number; x: number;
y: number; y: number;
bbox: IRect | null; bbox: IRect | null;
bboxNeedsUpdate: boolean; bboxNeedsUpdate: boolean;
isVisible: boolean;
}; };
type ControlLayer = LayerBase & { type ControlAdapterLayer = RenderableLayerBase & {
type: 'control_layer'; type: 'controlnet_layer'; // technically, also t2i adapter layer
controlAdapter: ControlAdapterConfig; controlAdapterId: string;
}; };
type MaskLayerBase = LayerBase & { type IPAdapterLayer = LayerBase & {
positivePrompt: string | null; type: 'ip_adapter_layer'; // technically, also t2i adapter layer
negativePrompt: string | null; // Up to one text prompt per mask 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 ipAdapterIds: string[]; // Any number of image prompts
previewColor: RgbColor; previewColor: RgbColor;
autoNegative: ParameterAutoNegative; autoNegative: ParameterAutoNegative;
needsPixelBbox: boolean; // Needs the slower pixel-based bbox calculation - set to true when an there is an eraser object needsPixelBbox: boolean; // Needs the slower pixel-based bbox calculation - set to true when an there is an eraser object
}; };
export type VectorMaskLayer = MaskLayerBase & { export type Layer = MaskedGuidanceLayer | ControlAdapterLayer | IPAdapterLayer;
type: 'vector_mask_layer';
objects: (VectorMaskLine | VectorMaskRect)[];
};
export type Layer = VectorMaskLayer | ControlLayer;
type BaseLayerState = {
positivePrompt: ParameterPositivePrompt;
negativePrompt: ParameterNegativePrompt;
positivePrompt2: ParameterPositiveStylePromptSDXL;
negativePrompt2: ParameterNegativeStylePromptSDXL;
shouldConcatPrompts: boolean;
};
type RegionalPromptsState = { type RegionalPromptsState = {
_version: 1; _version: 1;
@ -94,7 +90,12 @@ type RegionalPromptsState = {
brushSize: number; brushSize: number;
globalMaskLayerOpacity: number; globalMaskLayerOpacity: number;
isEnabled: boolean; isEnabled: boolean;
baseLayer: BaseLayerState; positivePrompt: ParameterPositivePrompt;
negativePrompt: ParameterNegativePrompt;
positivePrompt2: ParameterPositiveStylePromptSDXL;
negativePrompt2: ParameterNegativeStylePromptSDXL;
shouldConcatPrompts: boolean;
initialImage: string | null;
size: { size: {
width: ParameterWidth; width: ParameterWidth;
height: ParameterHeight; height: ParameterHeight;
@ -109,13 +110,12 @@ export const initialRegionalPromptsState: RegionalPromptsState = {
layers: [], layers: [],
globalMaskLayerOpacity: 0.5, // this globally changes all mask layers' opacity globalMaskLayerOpacity: 0.5, // this globally changes all mask layers' opacity
isEnabled: true, isEnabled: true,
baseLayer: { positivePrompt: '',
positivePrompt: '', negativePrompt: '',
negativePrompt: '', positivePrompt2: '',
positivePrompt2: '', negativePrompt2: '',
negativePrompt2: '', shouldConcatPrompts: true,
shouldConcatPrompts: true, initialImage: null,
},
size: { size: {
width: 512, width: 512,
height: 512, height: 512,
@ -124,10 +124,13 @@ export const initialRegionalPromptsState: RegionalPromptsState = {
}; };
const isLine = (obj: VectorMaskLine | VectorMaskRect): obj is VectorMaskLine => obj.type === 'vector_mask_line'; 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) => { const resetLayer = (layer: Layer) => {
if (layer.type === 'vector_mask_layer') { if (layer.type === 'masked_guidance_layer') {
layer.objects = []; layer.maskObjects = [];
layer.bbox = null; layer.bbox = null;
layer.isVisible = true; layer.isVisible = true;
layer.needsPixelBbox = false; layer.needsPixelBbox = false;
@ -135,12 +138,12 @@ const resetLayer = (layer: Layer) => {
return; return;
} }
if (layer.type === 'control_layer') { if (layer.type === 'controlnet_layer') {
// TODO // TODO
} }
}; };
const getVectorMaskPreviewColor = (state: RegionalPromptsState): RgbColor => { const getVectorMaskPreviewColor = (state: RegionalPromptsState): RgbColor => {
const vmLayers = state.layers.filter(isVectorMaskLayer); const vmLayers = state.layers.filter(isMaskedGuidanceLayer);
const lastColor = vmLayers[vmLayers.length - 1]?.previewColor; const lastColor = vmLayers[vmLayers.length - 1]?.previewColor;
return LayerColors.next(lastColor); return LayerColors.next(lastColor);
}; };
@ -153,14 +156,14 @@ export const regionalPromptsSlice = createSlice({
layerAdded: { layerAdded: {
reducer: (state, action: PayloadAction<Layer['type'], string, { uuid: string }>) => { reducer: (state, action: PayloadAction<Layer['type'], string, { uuid: string }>) => {
const type = action.payload; const type = action.payload;
if (type === 'vector_mask_layer') { if (type === 'masked_guidance_layer') {
const layer: VectorMaskLayer = { const layer: MaskedGuidanceLayer = {
id: getVectorMaskLayerId(action.meta.uuid), id: getMaskedGuidanceLayerId(action.meta.uuid),
type, type: 'masked_guidance_layer',
isVisible: true, isVisible: true,
bbox: null, bbox: null,
bboxNeedsUpdate: false, bboxNeedsUpdate: false,
objects: [], maskObjects: [],
previewColor: getVectorMaskPreviewColor(state), previewColor: getVectorMaskPreviewColor(state),
x: 0, x: 0,
y: 0, y: 0,
@ -175,8 +178,19 @@ export const regionalPromptsSlice = createSlice({
return; return;
} }
if (type === 'control_layer') { if (type === 'controlnet_layer') {
// TODO 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; return;
} }
}, },
@ -197,7 +211,7 @@ export const regionalPromptsSlice = createSlice({
layerTranslated: (state, action: PayloadAction<{ layerId: string; x: number; y: number }>) => { layerTranslated: (state, action: PayloadAction<{ layerId: string; x: number; y: number }>) => {
const { layerId, x, y } = action.payload; const { layerId, x, y } = action.payload;
const layer = state.layers.find((l) => l.id === layerId); const layer = state.layers.find((l) => l.id === layerId);
if (layer) { if (isRenderableLayer(layer)) {
layer.x = x; layer.x = x;
layer.y = y; layer.y = y;
} }
@ -205,7 +219,7 @@ export const regionalPromptsSlice = createSlice({
layerBboxChanged: (state, action: PayloadAction<{ layerId: string; bbox: IRect | null }>) => { layerBboxChanged: (state, action: PayloadAction<{ layerId: string; bbox: IRect | null }>) => {
const { layerId, bbox } = action.payload; const { layerId, bbox } = action.payload;
const layer = state.layers.find((l) => l.id === layerId); const layer = state.layers.find((l) => l.id === layerId);
if (layer) { if (isRenderableLayer(layer)) {
layer.bbox = bbox; layer.bbox = bbox;
layer.bboxNeedsUpdate = false; layer.bboxNeedsUpdate = false;
} }
@ -258,21 +272,21 @@ export const regionalPromptsSlice = createSlice({
maskLayerPositivePromptChanged: (state, action: PayloadAction<{ layerId: string; prompt: string | null }>) => { maskLayerPositivePromptChanged: (state, action: PayloadAction<{ layerId: string; prompt: string | null }>) => {
const { layerId, prompt } = action.payload; const { layerId, prompt } = action.payload;
const layer = state.layers.find((l) => l.id === layerId); const layer = state.layers.find((l) => l.id === layerId);
if (layer?.type === 'vector_mask_layer') { if (layer?.type === 'masked_guidance_layer') {
layer.positivePrompt = prompt; layer.positivePrompt = prompt;
} }
}, },
maskLayerNegativePromptChanged: (state, action: PayloadAction<{ layerId: string; prompt: string | null }>) => { maskLayerNegativePromptChanged: (state, action: PayloadAction<{ layerId: string; prompt: string | null }>) => {
const { layerId, prompt } = action.payload; const { layerId, prompt } = action.payload;
const layer = state.layers.find((l) => l.id === layerId); const layer = state.layers.find((l) => l.id === layerId);
if (layer?.type === 'vector_mask_layer') { if (layer?.type === 'masked_guidance_layer') {
layer.negativePrompt = prompt; layer.negativePrompt = prompt;
} }
}, },
maskLayerIPAdapterAdded: { maskLayerIPAdapterAdded: {
reducer: (state, action: PayloadAction<string, string, { uuid: string }>) => { reducer: (state, action: PayloadAction<string, string, { uuid: string }>) => {
const layer = state.layers.find((l) => l.id === action.payload); 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); layer.ipAdapterIds.push(action.meta.uuid);
} }
}, },
@ -281,7 +295,7 @@ export const regionalPromptsSlice = createSlice({
maskLayerPreviewColorChanged: (state, action: PayloadAction<{ layerId: string; color: RgbColor }>) => { maskLayerPreviewColorChanged: (state, action: PayloadAction<{ layerId: string; color: RgbColor }>) => {
const { layerId, color } = action.payload; const { layerId, color } = action.payload;
const layer = state.layers.find((l) => l.id === layerId); const layer = state.layers.find((l) => l.id === layerId);
if (layer?.type === 'vector_mask_layer') { if (layer?.type === 'masked_guidance_layer') {
layer.previewColor = color; layer.previewColor = color;
} }
}, },
@ -296,9 +310,9 @@ export const regionalPromptsSlice = createSlice({
) => { ) => {
const { layerId, points, tool } = action.payload; const { layerId, points, tool } = action.payload;
const layer = state.layers.find((l) => l.id === layerId); const layer = state.layers.find((l) => l.id === layerId);
if (layer?.type === 'vector_mask_layer') { if (layer?.type === 'masked_guidance_layer') {
const lineId = getVectorMaskLayerLineId(layer.id, action.meta.uuid); const lineId = getMaskedGuidanceLayerLineId(layer.id, action.meta.uuid);
layer.objects.push({ layer.maskObjects.push({
type: 'vector_mask_line', type: 'vector_mask_line',
tool: tool, tool: tool,
id: lineId, id: lineId,
@ -321,8 +335,8 @@ export const regionalPromptsSlice = createSlice({
maskLayerPointsAdded: (state, action: PayloadAction<{ layerId: string; point: [number, number] }>) => { maskLayerPointsAdded: (state, action: PayloadAction<{ layerId: string; point: [number, number] }>) => {
const { layerId, point } = action.payload; const { layerId, point } = action.payload;
const layer = state.layers.find((l) => l.id === layerId); const layer = state.layers.find((l) => l.id === layerId);
if (layer?.type === 'vector_mask_layer') { if (layer?.type === 'masked_guidance_layer') {
const lastLine = layer.objects.findLast(isLine); const lastLine = layer.maskObjects.findLast(isLine);
if (!lastLine) { if (!lastLine) {
return; return;
} }
@ -340,9 +354,9 @@ export const regionalPromptsSlice = createSlice({
return; return;
} }
const layer = state.layers.find((l) => l.id === layerId); const layer = state.layers.find((l) => l.id === layerId);
if (layer?.type === 'vector_mask_layer') { if (layer?.type === 'masked_guidance_layer') {
const id = getVectorMaskLayerRectId(layer.id, action.meta.uuid); const id = getMaskedGuidnaceLayerRectId(layer.id, action.meta.uuid);
layer.objects.push({ layer.maskObjects.push({
type: 'vector_mask_rect', type: 'vector_mask_rect',
id, id,
x: rect.x - layer.x, x: rect.x - layer.x,
@ -361,7 +375,7 @@ export const regionalPromptsSlice = createSlice({
) => { ) => {
const { layerId, autoNegative } = action.payload; const { layerId, autoNegative } = action.payload;
const layer = state.layers.find((l) => l.id === layerId); const layer = state.layers.find((l) => l.id === layerId);
if (layer?.type === 'vector_mask_layer') { if (layer?.type === 'masked_guidance_layer') {
layer.autoNegative = autoNegative; layer.autoNegative = autoNegative;
} }
}, },
@ -369,19 +383,19 @@ export const regionalPromptsSlice = createSlice({
//#region Base Layer //#region Base Layer
positivePromptChanged: (state, action: PayloadAction<string>) => { positivePromptChanged: (state, action: PayloadAction<string>) => {
state.baseLayer.positivePrompt = action.payload; state.positivePrompt = action.payload;
}, },
negativePromptChanged: (state, action: PayloadAction<string>) => { negativePromptChanged: (state, action: PayloadAction<string>) => {
state.baseLayer.negativePrompt = action.payload; state.negativePrompt = action.payload;
}, },
positivePrompt2Changed: (state, action: PayloadAction<string>) => { positivePrompt2Changed: (state, action: PayloadAction<string>) => {
state.baseLayer.positivePrompt2 = action.payload; state.positivePrompt2 = action.payload;
}, },
negativePrompt2Changed: (state, action: PayloadAction<string>) => { negativePrompt2Changed: (state, action: PayloadAction<string>) => {
state.baseLayer.negativePrompt2 = action.payload; state.negativePrompt2 = action.payload;
}, },
shouldConcatPromptsChanged: (state, action: PayloadAction<boolean>) => { shouldConcatPromptsChanged: (state, action: PayloadAction<boolean>) => {
state.baseLayer.shouldConcatPrompts = action.payload; state.shouldConcatPrompts = action.payload;
}, },
widthChanged: (state, action: PayloadAction<{ width: number; updateAspectRatio?: boolean }>) => { widthChanged: (state, action: PayloadAction<{ width: number; updateAspectRatio?: boolean }>) => {
const { width, updateAspectRatio } = action.payload; const { width, updateAspectRatio } = action.payload;
@ -418,13 +432,13 @@ export const regionalPromptsSlice = createSlice({
}, },
undo: (state) => { undo: (state) => {
// Invalidate the bbox for all layers to prevent stale bboxes // 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; layer.bboxNeedsUpdate = true;
} }
}, },
redo: (state) => { redo: (state) => {
// Invalidate the bbox for all layers to prevent stale bboxes // 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; layer.bboxNeedsUpdate = true;
} }
}, },
@ -432,7 +446,7 @@ export const regionalPromptsSlice = createSlice({
}, },
extraReducers(builder) { extraReducers(builder) {
builder.addCase(controlAdapterRemoved, (state, action) => { 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); 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'; export const BACKGROUND_RECT_ID = 'background_layer.rect';
// Names (aka classes) for Konva layers and objects // Names (aka classes) for Konva layers and objects
export const VECTOR_MASK_LAYER_NAME = 'vector_mask_layer'; export const MASKED_GUIDANCE_LAYER_NAME = 'masked_guidance_layer';
export const VECTOR_MASK_LAYER_LINE_NAME = 'vector_mask_layer.line'; export const MASKED_GUIDANCE_LAYER_LINE_NAME = 'masked_guidance_layer.line';
export const VECTOR_MASK_LAYER_OBJECT_GROUP_NAME = 'vector_mask_layer.object_group'; export const MASKED_GUIDANCE_LAYER_OBJECT_GROUP_NAME = 'masked_guidance_layer.object_group';
export const VECTOR_MASK_LAYER_RECT_NAME = 'vector_mask_layer.rect'; export const MASKED_GUIDANCE_LAYER_RECT_NAME = 'masked_guidance_layer.rect';
export const LAYER_BBOX_NAME = 'layer.bbox'; export const LAYER_BBOX_NAME = 'layer.bbox';
// Getters for non-singleton layer and object IDs // Getters for non-singleton layer and object IDs
const getVectorMaskLayerId = (layerId: string) => `${VECTOR_MASK_LAYER_NAME}_${layerId}`; const getMaskedGuidanceLayerId = (layerId: string) => `${MASKED_GUIDANCE_LAYER_NAME}_${layerId}`;
const getVectorMaskLayerLineId = (layerId: string, lineId: string) => `${layerId}.line_${lineId}`; const getMaskedGuidanceLayerLineId = (layerId: string, lineId: string) => `${layerId}.line_${lineId}`;
const getVectorMaskLayerRectId = (layerId: string, lineId: string) => `${layerId}.rect_${lineId}`; const getMaskedGuidnaceLayerRectId = (layerId: string, lineId: string) => `${layerId}.rect_${lineId}`;
export const getVectorMaskLayerObjectGroupId = (layerId: string, groupId: string) => export const getMaskedGuidanceLayerObjectGroupId = (layerId: string, groupId: string) =>
`${layerId}.objectGroup_${groupId}`; `${layerId}.objectGroup_${groupId}`;
export const getLayerBboxId = (layerId: string) => `${layerId}.bbox`; export const getLayerBboxId = (layerId: string) => `${layerId}.bbox`;
const getControlLayerId = (layerId: string) => `control_layer_${layerId}`;
export const regionalPromptsPersistConfig: PersistConfig<RegionalPromptsState> = { export const regionalPromptsPersistConfig: PersistConfig<RegionalPromptsState> = {
name: regionalPromptsSlice.name, name: regionalPromptsSlice.name,

View File

@ -1,6 +1,6 @@
import openBase64ImageInTab from 'common/util/openBase64ImageInTab'; import openBase64ImageInTab from 'common/util/openBase64ImageInTab';
import { imageDataToDataURL } from 'features/canvas/util/blobToDataURL'; 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 Konva from 'konva';
import type { Layer as KonvaLayerType } from 'konva/lib/Layer'; import type { Layer as KonvaLayerType } from 'konva/lib/Layer';
import type { IRect } from 'konva/lib/types'; import type { IRect } from 'konva/lib/types';
@ -81,7 +81,7 @@ export const getLayerBboxPixels = (layer: KonvaLayerType, preview: boolean = fal
offscreenStage.add(layerClone); offscreenStage.add(layerClone);
for (const child of layerClone.getChildren()) { 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 // We need to cache the group to ensure it composites out eraser strokes correctly
child.opacity(1); child.opacity(1);
child.cache(); child.cache();

View File

@ -1,7 +1,7 @@
import { getStore } from 'app/store/nanostores/store'; import { getStore } from 'app/store/nanostores/store';
import openBase64ImageInTab from 'common/util/openBase64ImageInTab'; import openBase64ImageInTab from 'common/util/openBase64ImageInTab';
import { blobToDataURL } from 'features/canvas/util/blobToDataURL'; 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 { renderers } from 'features/regionalPrompts/util/renderers';
import Konva from 'konva'; import Konva from 'konva';
import { assert } from 'tsafe'; import { assert } from 'tsafe';
@ -19,12 +19,12 @@ export const getRegionalPromptLayerBlobs = async (
const state = getStore().getState(); const state = getStore().getState();
const { layers } = state.regionalPrompts.present; const { layers } = state.regionalPrompts.present;
const { width, height } = state.regionalPrompts.present.size; const { width, height } = state.regionalPrompts.present.size;
const reduxLayers = layers.filter(isVectorMaskLayer); const reduxLayers = layers.filter(isMaskedGuidanceLayer);
const container = document.createElement('div'); const container = document.createElement('div');
const stage = new Konva.Stage({ container, width, height }); const stage = new Konva.Stage({ container, width, height });
renderers.renderLayers(stage, reduxLayers, 1, 'brush'); renderers.renderLayers(stage, reduxLayers, 1, 'brush');
const konvaLayers = stage.find<Konva.Layer>(`.${VECTOR_MASK_LAYER_NAME}`); const konvaLayers = stage.find<Konva.Layer>(`.${MASKED_GUIDANCE_LAYER_NAME}`);
const blobs: Record<string, Blob> = {}; const blobs: Record<string, Blob> = {};
// First remove all layers // First remove all layers

View File

@ -3,8 +3,8 @@ import { rgbaColorToString, rgbColorToString } from 'features/canvas/util/colorT
import { getScaledFlooredCursorPosition } from 'features/regionalPrompts/hooks/mouseEventHooks'; import { getScaledFlooredCursorPosition } from 'features/regionalPrompts/hooks/mouseEventHooks';
import type { import type {
Layer, Layer,
MaskedGuidanceLayer,
Tool, Tool,
VectorMaskLayer,
VectorMaskLine, VectorMaskLine,
VectorMaskRect, VectorMaskRect,
} from 'features/regionalPrompts/store/regionalPromptsSlice'; } from 'features/regionalPrompts/store/regionalPromptsSlice';
@ -13,19 +13,19 @@ import {
BACKGROUND_LAYER_ID, BACKGROUND_LAYER_ID,
BACKGROUND_RECT_ID, BACKGROUND_RECT_ID,
getLayerBboxId, getLayerBboxId,
getVectorMaskLayerObjectGroupId, getMaskedGuidanceLayerObjectGroupId,
isVectorMaskLayer, isMaskedGuidanceLayer,
LAYER_BBOX_NAME, 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_INNER_ID,
TOOL_PREVIEW_BRUSH_BORDER_OUTER_ID, TOOL_PREVIEW_BRUSH_BORDER_OUTER_ID,
TOOL_PREVIEW_BRUSH_FILL_ID, TOOL_PREVIEW_BRUSH_FILL_ID,
TOOL_PREVIEW_BRUSH_GROUP_ID, TOOL_PREVIEW_BRUSH_GROUP_ID,
TOOL_PREVIEW_LAYER_ID, TOOL_PREVIEW_LAYER_ID,
TOOL_PREVIEW_RECT_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'; } from 'features/regionalPrompts/store/regionalPromptsSlice';
import { getLayerBboxFast, getLayerBboxPixels } from 'features/regionalPrompts/util/bbox'; import { getLayerBboxFast, getLayerBboxPixels } from 'features/regionalPrompts/util/bbox';
import Konva from 'konva'; import Konva from 'konva';
@ -54,7 +54,7 @@ const getIsSelected = (layerId?: string | null) => {
}; };
const selectVectorMaskObjects = (node: Konva.Node) => { 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, isMouseOver: boolean,
brushSize: number 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 // Update the stage's pointer style
if (layerCount === 0) { if (layerCount === 0) {
// We have no layers, so we should not render any tool // We have no layers, so we should not render any tool
@ -221,13 +221,13 @@ const renderToolPreview = (
*/ */
const createVectorMaskLayer = ( const createVectorMaskLayer = (
stage: Konva.Stage, stage: Konva.Stage,
reduxLayer: VectorMaskLayer, reduxLayer: MaskedGuidanceLayer,
onLayerPosChanged?: (layerId: string, x: number, y: number) => void onLayerPosChanged?: (layerId: string, x: number, y: number) => void
) => { ) => {
// This layer hasn't been added to the konva state yet // This layer hasn't been added to the konva state yet
const konvaLayer = new Konva.Layer({ const konvaLayer = new Konva.Layer({
id: reduxLayer.id, id: reduxLayer.id,
name: VECTOR_MASK_LAYER_NAME, name: MASKED_GUIDANCE_LAYER_NAME,
draggable: true, draggable: true,
dragDistance: 0, dragDistance: 0,
}); });
@ -259,8 +259,8 @@ const createVectorMaskLayer = (
// The object group holds all of the layer's objects (e.g. lines and rects) // The object group holds all of the layer's objects (e.g. lines and rects)
const konvaObjectGroup = new Konva.Group({ const konvaObjectGroup = new Konva.Group({
id: getVectorMaskLayerObjectGroupId(reduxLayer.id, uuidv4()), id: getMaskedGuidanceLayerObjectGroupId(reduxLayer.id, uuidv4()),
name: VECTOR_MASK_LAYER_OBJECT_GROUP_NAME, name: MASKED_GUIDANCE_LAYER_OBJECT_GROUP_NAME,
listening: false, listening: false,
}); });
konvaLayer.add(konvaObjectGroup); konvaLayer.add(konvaObjectGroup);
@ -279,7 +279,7 @@ const createVectorMaskLine = (reduxObject: VectorMaskLine, konvaGroup: Konva.Gro
const vectorMaskLine = new Konva.Line({ const vectorMaskLine = new Konva.Line({
id: reduxObject.id, id: reduxObject.id,
key: reduxObject.id, key: reduxObject.id,
name: VECTOR_MASK_LAYER_LINE_NAME, name: MASKED_GUIDANCE_LAYER_LINE_NAME,
strokeWidth: reduxObject.strokeWidth, strokeWidth: reduxObject.strokeWidth,
tension: 0, tension: 0,
lineCap: 'round', lineCap: 'round',
@ -301,7 +301,7 @@ const createVectorMaskRect = (reduxObject: VectorMaskRect, konvaGroup: Konva.Gro
const vectorMaskRect = new Konva.Rect({ const vectorMaskRect = new Konva.Rect({
id: reduxObject.id, id: reduxObject.id,
key: reduxObject.id, key: reduxObject.id,
name: VECTOR_MASK_LAYER_RECT_NAME, name: MASKED_GUIDANCE_LAYER_RECT_NAME,
x: reduxObject.x, x: reduxObject.x,
y: reduxObject.y, y: reduxObject.y,
width: reduxObject.width, width: reduxObject.width,
@ -322,7 +322,7 @@ const createVectorMaskRect = (reduxObject: VectorMaskRect, konvaGroup: Konva.Gro
*/ */
const renderVectorMaskLayer = ( const renderVectorMaskLayer = (
stage: Konva.Stage, stage: Konva.Stage,
reduxLayer: VectorMaskLayer, reduxLayer: MaskedGuidanceLayer,
globalMaskLayerOpacity: number, globalMaskLayerOpacity: number,
tool: Tool, tool: Tool,
onLayerPosChanged?: (layerId: string, x: number, y: number) => void 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. // Convert the color to a string, stripping the alpha - the object group will handle opacity.
const rgbColor = rgbColorToString(reduxLayer.previewColor); const rgbColor = rgbColorToString(reduxLayer.previewColor);
const konvaObjectGroup = konvaLayer.findOne<Konva.Group>(`.${VECTOR_MASK_LAYER_OBJECT_GROUP_NAME}`); const konvaObjectGroup = konvaLayer.findOne<Konva.Group>(`.${MASKED_GUIDANCE_LAYER_OBJECT_GROUP_NAME}`);
assert(konvaObjectGroup, `Object group not found for layer ${reduxLayer.id}`); 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. // We use caching to handle "global" layer opacity, but caching is expensive and we should only do it when required.
let groupNeedsCache = false; let groupNeedsCache = false;
const objectIds = reduxLayer.objects.map(mapId); const objectIds = reduxLayer.maskObjects.map(mapId);
for (const objectNode of konvaObjectGroup.find(selectVectorMaskObjects)) { for (const objectNode of konvaObjectGroup.find(selectVectorMaskObjects)) {
if (!objectIds.includes(objectNode.id())) { if (!objectIds.includes(objectNode.id())) {
objectNode.destroy(); 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') { if (reduxObject.type === 'vector_mask_line') {
const vectorMaskLine = const vectorMaskLine =
stage.findOne<Konva.Line>(`#${reduxObject.id}`) ?? createVectorMaskLine(reduxObject, konvaObjectGroup); stage.findOne<Konva.Line>(`#${reduxObject.id}`) ?? createVectorMaskLine(reduxObject, konvaObjectGroup);
@ -419,14 +419,14 @@ const renderLayers = (
const reduxLayerIds = reduxLayers.map(mapId); const reduxLayerIds = reduxLayers.map(mapId);
// Remove un-rendered layers // Remove un-rendered layers
for (const konvaLayer of stage.find<Konva.Layer>(`.${VECTOR_MASK_LAYER_NAME}`)) { for (const konvaLayer of stage.find<Konva.Layer>(`.${MASKED_GUIDANCE_LAYER_NAME}`)) {
if (!reduxLayerIds.includes(konvaLayer.id())) { if (!reduxLayerIds.includes(konvaLayer.id())) {
konvaLayer.destroy(); konvaLayer.destroy();
} }
} }
for (const reduxLayer of reduxLayers) { for (const reduxLayer of reduxLayers) {
if (isVectorMaskLayer(reduxLayer)) { if (isMaskedGuidanceLayer(reduxLayer)) {
renderVectorMaskLayer(stage, reduxLayer, globalMaskLayerOpacity, tool, onLayerPosChanged); renderVectorMaskLayer(stage, reduxLayer, globalMaskLayerOpacity, tool, onLayerPosChanged);
} }
} }
@ -494,14 +494,14 @@ const renderBbox = (
} }
for (const reduxLayer of reduxLayers) { for (const reduxLayer of reduxLayers) {
if (reduxLayer.type === 'vector_mask_layer') { if (reduxLayer.type === 'masked_guidance_layer') {
const konvaLayer = stage.findOne<Konva.Layer>(`#${reduxLayer.id}`); const konvaLayer = stage.findOne<Konva.Layer>(`#${reduxLayer.id}`);
assert(konvaLayer, `Layer ${reduxLayer.id} not found in stage`); assert(konvaLayer, `Layer ${reduxLayer.id} not found in stage`);
let bbox = reduxLayer.bbox; let bbox = reduxLayer.bbox;
// We only need to recalculate the bbox if the layer has changed and it has objects // 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 // We only need to use the pixel-perfect bounding box if the layer has eraser strokes
bbox = reduxLayer.needsPixelBbox ? getLayerBboxPixels(konvaLayer) : getLayerBboxFast(konvaLayer); bbox = reduxLayer.needsPixelBbox ? getLayerBboxPixels(konvaLayer) : getLayerBboxFast(konvaLayer);
// Update the layer's bbox in the redux store // Update the layer's bbox in the redux store

View File

@ -11,7 +11,7 @@ import { useTranslation } from 'react-i18next';
export const ParamSDXLNegativeStylePrompt = memo(() => { export const ParamSDXLNegativeStylePrompt = memo(() => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const prompt = useAppSelector((s) => s.regionalPrompts.present.baseLayer.negativePrompt2); const prompt = useAppSelector((s) => s.regionalPrompts.present.negativePrompt2);
const textareaRef = useRef<HTMLTextAreaElement>(null); const textareaRef = useRef<HTMLTextAreaElement>(null);
const { t } = useTranslation(); const { t } = useTranslation();
const handleChange = useCallback( const handleChange = useCallback(

View File

@ -10,7 +10,7 @@ import { useTranslation } from 'react-i18next';
export const ParamSDXLPositiveStylePrompt = memo(() => { export const ParamSDXLPositiveStylePrompt = memo(() => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const prompt = useAppSelector((s) => s.regionalPrompts.present.baseLayer.positivePrompt2); const prompt = useAppSelector((s) => s.regionalPrompts.present.positivePrompt2);
const textareaRef = useRef<HTMLTextAreaElement>(null); const textareaRef = useRef<HTMLTextAreaElement>(null);
const { t } = useTranslation(); const { t } = useTranslation();
const handleChange = useCallback( const handleChange = useCallback(

View File

@ -6,7 +6,7 @@ import { useTranslation } from 'react-i18next';
import { PiLinkSimpleBold, PiLinkSimpleBreakBold } from 'react-icons/pi'; import { PiLinkSimpleBold, PiLinkSimpleBreakBold } from 'react-icons/pi';
export const SDXLConcatButton = memo(() => { 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 dispatch = useAppDispatch();
const { t } = useTranslation(); const { t } = useTranslation();

View File

@ -8,7 +8,7 @@ import { ParamSDXLNegativeStylePrompt } from './ParamSDXLNegativeStylePrompt';
import { ParamSDXLPositiveStylePrompt } from './ParamSDXLPositiveStylePrompt'; import { ParamSDXLPositiveStylePrompt } from './ParamSDXLPositiveStylePrompt';
export const SDXLPrompts = memo(() => { export const SDXLPrompts = memo(() => {
const shouldConcatPrompts = useAppSelector((s) => s.regionalPrompts.present.baseLayer.shouldConcatPrompts); const shouldConcatPrompts = useAppSelector((s) => s.regionalPrompts.present.shouldConcatPrompts);
return ( return (
<Flex flexDir="column" gap={2} pos="relative"> <Flex flexDir="column" gap={2} pos="relative">
<ParamPositivePrompt /> <ParamPositivePrompt />

View File

@ -13,7 +13,7 @@ import {
selectValidIPAdapters, selectValidIPAdapters,
selectValidT2IAdapters, selectValidT2IAdapters,
} from 'features/controlAdapters/store/controlAdaptersSlice'; } 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 { useStandaloneAccordionToggle } from 'features/settingsAccordions/hooks/useStandaloneAccordionToggle';
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus'; import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
import { Fragment, memo } from 'react'; import { Fragment, memo } from 'react';
@ -28,7 +28,7 @@ const selector = createMemoizedSelector(
const enabledNonRegionalIPAdapterCount = selectAllIPAdapters(controlAdapters) const enabledNonRegionalIPAdapterCount = selectAllIPAdapters(controlAdapters)
.filter( .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; .filter((ca) => ca.isEnabled).length;
@ -59,7 +59,7 @@ const selector = createMemoizedSelector(
} }
const controlAdapterIds = selectControlAdapterIds(controlAdapters).filter( 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 { return {