mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
feat(ui): revise internal state for RCC
This commit is contained in:
parent
2f6fec8c6c
commit
6f5f3381f9
@ -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')) {
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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');
|
||||
|
@ -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');
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
@ -44,7 +44,7 @@ export const buildCanvasTextToImageGraph = async (state: RootState): Promise<Non
|
||||
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;
|
||||
|
@ -10,7 +10,7 @@ import { getHasMetadata, removeMetadata } from './metadata';
|
||||
|
||||
export const prepareLinearUIBatch = (state: RootState, graph: NonNullableGraph, prepend: boolean): BatchConfig => {
|
||||
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'] = [];
|
||||
|
@ -53,7 +53,7 @@ export const buildLinearImageToImageGraph = async (state: RootState): Promise<No
|
||||
seamlessXAxis,
|
||||
seamlessYAxis,
|
||||
} = state.generation;
|
||||
const { positivePrompt, negativePrompt } = state.regionalPrompts.present.baseLayer;
|
||||
const { positivePrompt, negativePrompt } = state.regionalPrompts.present;
|
||||
const { width, height } = state.regionalPrompts.present.size;
|
||||
|
||||
/**
|
||||
|
@ -53,7 +53,7 @@ export const buildLinearSDXLImageToImageGraph = async (state: RootState): Promis
|
||||
seamlessYAxis,
|
||||
img2imgStrength: strength,
|
||||
} = state.generation;
|
||||
const { positivePrompt, negativePrompt } = state.regionalPrompts.present.baseLayer;
|
||||
const { positivePrompt, negativePrompt } = state.regionalPrompts.present;
|
||||
const { width, height } = state.regionalPrompts.present.size;
|
||||
|
||||
const { refinerModel, refinerStart } = state.sdxl;
|
||||
|
@ -41,7 +41,7 @@ export const buildLinearSDXLTextToImageGraph = async (state: RootState): Promise
|
||||
seamlessXAxis,
|
||||
seamlessYAxis,
|
||||
} = state.generation;
|
||||
const { positivePrompt, negativePrompt } = state.regionalPrompts.present.baseLayer;
|
||||
const { positivePrompt, negativePrompt } = state.regionalPrompts.present;
|
||||
const { width, height } = state.regionalPrompts.present.size;
|
||||
|
||||
const { refinerModel, refinerStart } = state.sdxl;
|
||||
|
@ -42,7 +42,7 @@ export const buildLinearTextToImageGraph = async (state: RootState): Promise<Non
|
||||
seamlessYAxis,
|
||||
seed,
|
||||
} = state.generation;
|
||||
const { positivePrompt, negativePrompt } = state.regionalPrompts.present.baseLayer;
|
||||
const { positivePrompt, negativePrompt } = state.regionalPrompts.present;
|
||||
const { width, height } = state.regionalPrompts.present.size;
|
||||
|
||||
const use_cpu = shouldUseCpuNoise;
|
||||
|
@ -18,7 +18,7 @@ export const getBoardField = (state: RootState): BoardField | undefined => {
|
||||
*/
|
||||
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,
|
||||
|
@ -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<HTMLTextAreaElement>(null);
|
||||
const { t } = useTranslation();
|
||||
const _onChange = useCallback(
|
||||
|
@ -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<HTMLTextAreaElement>(null);
|
||||
|
@ -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 = {
|
||||
|
@ -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 (
|
||||
|
@ -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,
|
||||
|
@ -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]
|
||||
|
@ -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]
|
||||
|
@ -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;
|
||||
}),
|
||||
|
@ -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,
|
||||
|
@ -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 {
|
||||
|
@ -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()
|
||||
);
|
||||
|
@ -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;
|
||||
});
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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]
|
||||
|
@ -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);
|
||||
|
@ -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<Layer['type'], string, { uuid: string }>) => {
|
||||
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<string, string, { uuid: string }>) => {
|
||||
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<string>) => {
|
||||
state.baseLayer.positivePrompt = action.payload;
|
||||
state.positivePrompt = action.payload;
|
||||
},
|
||||
negativePromptChanged: (state, action: PayloadAction<string>) => {
|
||||
state.baseLayer.negativePrompt = action.payload;
|
||||
state.negativePrompt = action.payload;
|
||||
},
|
||||
positivePrompt2Changed: (state, action: PayloadAction<string>) => {
|
||||
state.baseLayer.positivePrompt2 = action.payload;
|
||||
state.positivePrompt2 = action.payload;
|
||||
},
|
||||
negativePrompt2Changed: (state, action: PayloadAction<string>) => {
|
||||
state.baseLayer.negativePrompt2 = action.payload;
|
||||
state.negativePrompt2 = action.payload;
|
||||
},
|
||||
shouldConcatPromptsChanged: (state, action: PayloadAction<boolean>) => {
|
||||
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<RegionalPromptsState> = {
|
||||
name: regionalPromptsSlice.name,
|
||||
|
@ -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();
|
||||
|
@ -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<Konva.Layer>(`.${VECTOR_MASK_LAYER_NAME}`);
|
||||
const konvaLayers = stage.find<Konva.Layer>(`.${MASKED_GUIDANCE_LAYER_NAME}`);
|
||||
const blobs: Record<string, Blob> = {};
|
||||
|
||||
// First remove all layers
|
||||
|
@ -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<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}`);
|
||||
|
||||
// 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<Konva.Line>(`#${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<Konva.Layer>(`.${VECTOR_MASK_LAYER_NAME}`)) {
|
||||
for (const konvaLayer of stage.find<Konva.Layer>(`.${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<Konva.Layer>(`#${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
|
||||
|
@ -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<HTMLTextAreaElement>(null);
|
||||
const { t } = useTranslation();
|
||||
const handleChange = useCallback(
|
||||
|
@ -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<HTMLTextAreaElement>(null);
|
||||
const { t } = useTranslation();
|
||||
const handleChange = useCallback(
|
||||
|
@ -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();
|
||||
|
@ -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 (
|
||||
<Flex flexDir="column" gap={2} pos="relative">
|
||||
<ParamPositivePrompt />
|
||||
|
@ -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 {
|
||||
|
Loading…
Reference in New Issue
Block a user