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 }) => {
|
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')) {
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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();
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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;
|
||||||
|
@ -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');
|
||||||
|
@ -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');
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
|
@ -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'] = [];
|
||||||
|
@ -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;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
|
@ -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,
|
||||||
|
@ -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(
|
||||||
|
@ -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);
|
||||||
|
@ -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 = {
|
||||||
|
@ -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 (
|
||||||
|
@ -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,
|
||||||
|
@ -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]
|
||||||
|
@ -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]
|
||||||
|
@ -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;
|
||||||
}),
|
}),
|
||||||
|
@ -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,
|
||||||
|
@ -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 {
|
||||||
|
@ -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()
|
||||||
);
|
);
|
||||||
|
@ -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;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
@ -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]
|
||||||
|
@ -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);
|
||||||
|
@ -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,
|
||||||
|
@ -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();
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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(
|
||||||
|
@ -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(
|
||||||
|
@ -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();
|
||||||
|
@ -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 />
|
||||||
|
@ -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 {
|
||||||
|
Loading…
Reference in New Issue
Block a user