mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
refactor(ui): add control layers separate control adapter implementation (wip)
- Revise control adapter config types - Recreate all control adapter mutations in control layers slice - Bit of renaming along the way - typing 'RegionalGuidanceLayer' over and over again was getting tedious
This commit is contained in:
parent
3717321480
commit
121918352a
@ -5,12 +5,12 @@ import { controlAdapterAdded, controlAdapterRemoved } from 'features/controlAdap
|
|||||||
import type { ControlNetConfig, IPAdapterConfig } from 'features/controlAdapters/store/types';
|
import type { ControlNetConfig, IPAdapterConfig } from 'features/controlAdapters/store/types';
|
||||||
import { isControlAdapterProcessorType } from 'features/controlAdapters/store/types';
|
import { isControlAdapterProcessorType } from 'features/controlAdapters/store/types';
|
||||||
import {
|
import {
|
||||||
controlAdapterLayerAdded,
|
caLayerAdded,
|
||||||
ipAdapterLayerAdded,
|
ipaLayerAdded,
|
||||||
layerDeleted,
|
layerDeleted,
|
||||||
maskLayerIPAdapterAdded,
|
rgLayerAdded,
|
||||||
maskLayerIPAdapterDeleted,
|
rgLayerIPAdapterAdded,
|
||||||
regionalGuidanceLayerAdded,
|
rgLayerIPAdapterDeleted,
|
||||||
} from 'features/controlLayers/store/controlLayersSlice';
|
} from 'features/controlLayers/store/controlLayersSlice';
|
||||||
import type { Layer } from 'features/controlLayers/store/types';
|
import type { Layer } from 'features/controlLayers/store/types';
|
||||||
import { modelConfigsAdapterSelectors, modelsApi } from 'services/api/endpoints/models';
|
import { modelConfigsAdapterSelectors, modelsApi } from 'services/api/endpoints/models';
|
||||||
@ -33,7 +33,7 @@ export const addControlLayersToControlAdapterBridge = (startAppListening: AppSta
|
|||||||
const type = action.payload;
|
const type = action.payload;
|
||||||
const layerId = uuidv4();
|
const layerId = uuidv4();
|
||||||
if (type === 'regional_guidance_layer') {
|
if (type === 'regional_guidance_layer') {
|
||||||
dispatch(regionalGuidanceLayerAdded({ layerId }));
|
dispatch(rgLayerAdded({ layerId }));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,7 +53,7 @@ export const addControlLayersToControlAdapterBridge = (startAppListening: AppSta
|
|||||||
overrides.model = models.find((m) => m.base === baseModel) ?? null;
|
overrides.model = models.find((m) => m.base === baseModel) ?? null;
|
||||||
}
|
}
|
||||||
dispatch(controlAdapterAdded({ type: 'ip_adapter', overrides }));
|
dispatch(controlAdapterAdded({ type: 'ip_adapter', overrides }));
|
||||||
dispatch(ipAdapterLayerAdded({ layerId, ipAdapterId }));
|
dispatch(ipaLayerAdded({ layerId, ipAdapterId }));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,7 +73,7 @@ export const addControlLayersToControlAdapterBridge = (startAppListening: AppSta
|
|||||||
overrides.processorNode = CONTROLNET_PROCESSORS[overrides.processorType].buildDefaults(baseModel);
|
overrides.processorNode = CONTROLNET_PROCESSORS[overrides.processorType].buildDefaults(baseModel);
|
||||||
}
|
}
|
||||||
dispatch(controlAdapterAdded({ type: 'controlnet', overrides }));
|
dispatch(controlAdapterAdded({ type: 'controlnet', overrides }));
|
||||||
dispatch(controlAdapterLayerAdded({ layerId, controlNetId }));
|
dispatch(caLayerAdded({ layerId, controlNetId }));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -129,7 +129,7 @@ export const addControlLayersToControlAdapterBridge = (startAppListening: AppSta
|
|||||||
}
|
}
|
||||||
|
|
||||||
dispatch(controlAdapterAdded({ type: 'ip_adapter', overrides }));
|
dispatch(controlAdapterAdded({ type: 'ip_adapter', overrides }));
|
||||||
dispatch(maskLayerIPAdapterAdded({ layerId, ipAdapterId }));
|
dispatch(rgLayerIPAdapterAdded({ layerId, ipAdapterId }));
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -138,7 +138,7 @@ export const addControlLayersToControlAdapterBridge = (startAppListening: AppSta
|
|||||||
effect: (action, { dispatch }) => {
|
effect: (action, { dispatch }) => {
|
||||||
const { layerId, ipAdapterId } = action.payload;
|
const { layerId, ipAdapterId } = action.payload;
|
||||||
dispatch(controlAdapterRemoved({ id: ipAdapterId }));
|
dispatch(controlAdapterRemoved({ id: ipAdapterId }));
|
||||||
dispatch(maskLayerIPAdapterDeleted({ layerId, ipAdapterId }));
|
dispatch(rgLayerIPAdapterDeleted({ layerId, ipAdapterId }));
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -4,8 +4,8 @@ import { guidanceLayerIPAdapterAdded } from 'app/store/middleware/listenerMiddle
|
|||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import {
|
import {
|
||||||
isRegionalGuidanceLayer,
|
isRegionalGuidanceLayer,
|
||||||
maskLayerNegativePromptChanged,
|
rgLayerNegativePromptChanged,
|
||||||
maskLayerPositivePromptChanged,
|
rgLayerPositivePromptChanged,
|
||||||
selectControlLayersSlice,
|
selectControlLayersSlice,
|
||||||
} from 'features/controlLayers/store/controlLayersSlice';
|
} from 'features/controlLayers/store/controlLayersSlice';
|
||||||
import { useCallback, useMemo } from 'react';
|
import { useCallback, useMemo } from 'react';
|
||||||
@ -33,10 +33,10 @@ export const AddPromptButtons = ({ layerId }: AddPromptButtonProps) => {
|
|||||||
);
|
);
|
||||||
const validActions = useAppSelector(selectValidActions);
|
const validActions = useAppSelector(selectValidActions);
|
||||||
const addPositivePrompt = useCallback(() => {
|
const addPositivePrompt = useCallback(() => {
|
||||||
dispatch(maskLayerPositivePromptChanged({ layerId, prompt: '' }));
|
dispatch(rgLayerPositivePromptChanged({ layerId, prompt: '' }));
|
||||||
}, [dispatch, layerId]);
|
}, [dispatch, layerId]);
|
||||||
const addNegativePrompt = useCallback(() => {
|
const addNegativePrompt = useCallback(() => {
|
||||||
dispatch(maskLayerNegativePromptChanged({ layerId, prompt: '' }));
|
dispatch(rgLayerNegativePromptChanged({ layerId, prompt: '' }));
|
||||||
}, [dispatch, layerId]);
|
}, [dispatch, layerId]);
|
||||||
const addIPAdapter = useCallback(() => {
|
const addIPAdapter = useCallback(() => {
|
||||||
dispatch(guidanceLayerIPAdapterAdded(layerId));
|
dispatch(guidanceLayerIPAdapterAdded(layerId));
|
||||||
|
@ -15,7 +15,7 @@ import {
|
|||||||
import { useAppDispatch } from 'app/store/storeHooks';
|
import { useAppDispatch } from 'app/store/storeHooks';
|
||||||
import { stopPropagation } from 'common/util/stopPropagation';
|
import { stopPropagation } from 'common/util/stopPropagation';
|
||||||
import { useLayerOpacity } from 'features/controlLayers/hooks/layerStateHooks';
|
import { useLayerOpacity } from 'features/controlLayers/hooks/layerStateHooks';
|
||||||
import { isFilterEnabledChanged, layerOpacityChanged } from 'features/controlLayers/store/controlLayersSlice';
|
import { caLayerIsFilterEnabledChanged, layerOpacityChanged } from 'features/controlLayers/store/controlLayersSlice';
|
||||||
import type { ChangeEvent } from 'react';
|
import type { ChangeEvent } from 'react';
|
||||||
import { memo, useCallback } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@ -40,7 +40,7 @@ const CALayerOpacity = ({ layerId }: Props) => {
|
|||||||
);
|
);
|
||||||
const onChangeFilter = useCallback(
|
const onChangeFilter = useCallback(
|
||||||
(e: ChangeEvent<HTMLInputElement>) => {
|
(e: ChangeEvent<HTMLInputElement>) => {
|
||||||
dispatch(isFilterEnabledChanged({ layerId, isFilterEnabled: e.target.checked }));
|
dispatch(caLayerIsFilterEnabledChanged({ layerId, isFilterEnabled: e.target.checked }));
|
||||||
},
|
},
|
||||||
[dispatch, layerId]
|
[dispatch, layerId]
|
||||||
);
|
);
|
||||||
|
@ -4,8 +4,8 @@ import { guidanceLayerIPAdapterAdded } from 'app/store/middleware/listenerMiddle
|
|||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import {
|
import {
|
||||||
isRegionalGuidanceLayer,
|
isRegionalGuidanceLayer,
|
||||||
maskLayerNegativePromptChanged,
|
rgLayerNegativePromptChanged,
|
||||||
maskLayerPositivePromptChanged,
|
rgLayerPositivePromptChanged,
|
||||||
selectControlLayersSlice,
|
selectControlLayersSlice,
|
||||||
} from 'features/controlLayers/store/controlLayersSlice';
|
} from 'features/controlLayers/store/controlLayersSlice';
|
||||||
import { memo, useCallback, useMemo } from 'react';
|
import { memo, useCallback, useMemo } from 'react';
|
||||||
@ -32,10 +32,10 @@ export const LayerMenuRGActions = memo(({ layerId }: Props) => {
|
|||||||
);
|
);
|
||||||
const validActions = useAppSelector(selectValidActions);
|
const validActions = useAppSelector(selectValidActions);
|
||||||
const addPositivePrompt = useCallback(() => {
|
const addPositivePrompt = useCallback(() => {
|
||||||
dispatch(maskLayerPositivePromptChanged({ layerId, prompt: '' }));
|
dispatch(rgLayerPositivePromptChanged({ layerId, prompt: '' }));
|
||||||
}, [dispatch, layerId]);
|
}, [dispatch, layerId]);
|
||||||
const addNegativePrompt = useCallback(() => {
|
const addNegativePrompt = useCallback(() => {
|
||||||
dispatch(maskLayerNegativePromptChanged({ layerId, prompt: '' }));
|
dispatch(rgLayerNegativePromptChanged({ layerId, prompt: '' }));
|
||||||
}, [dispatch, layerId]);
|
}, [dispatch, layerId]);
|
||||||
const addIPAdapter = useCallback(() => {
|
const addIPAdapter = useCallback(() => {
|
||||||
dispatch(guidanceLayerIPAdapterAdded(layerId));
|
dispatch(guidanceLayerIPAdapterAdded(layerId));
|
||||||
|
@ -3,7 +3,7 @@ import { createSelector } from '@reduxjs/toolkit';
|
|||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import {
|
import {
|
||||||
isRegionalGuidanceLayer,
|
isRegionalGuidanceLayer,
|
||||||
maskLayerAutoNegativeChanged,
|
rgLayerAutoNegativeChanged,
|
||||||
selectControlLayersSlice,
|
selectControlLayersSlice,
|
||||||
} from 'features/controlLayers/store/controlLayersSlice';
|
} from 'features/controlLayers/store/controlLayersSlice';
|
||||||
import type { ChangeEvent } from 'react';
|
import type { ChangeEvent } from 'react';
|
||||||
@ -35,7 +35,7 @@ export const RGLayerAutoNegativeCheckbox = memo(({ layerId }: Props) => {
|
|||||||
const autoNegative = useAutoNegative(layerId);
|
const autoNegative = useAutoNegative(layerId);
|
||||||
const onChange = useCallback(
|
const onChange = useCallback(
|
||||||
(e: ChangeEvent<HTMLInputElement>) => {
|
(e: ChangeEvent<HTMLInputElement>) => {
|
||||||
dispatch(maskLayerAutoNegativeChanged({ layerId, autoNegative: e.target.checked ? 'invert' : 'off' }));
|
dispatch(rgLayerAutoNegativeChanged({ layerId, autoNegative: e.target.checked ? 'invert' : 'off' }));
|
||||||
},
|
},
|
||||||
[dispatch, layerId]
|
[dispatch, layerId]
|
||||||
);
|
);
|
||||||
|
@ -6,7 +6,7 @@ import { stopPropagation } from 'common/util/stopPropagation';
|
|||||||
import { rgbColorToString } from 'features/canvas/util/colorToString';
|
import { rgbColorToString } from 'features/canvas/util/colorToString';
|
||||||
import {
|
import {
|
||||||
isRegionalGuidanceLayer,
|
isRegionalGuidanceLayer,
|
||||||
maskLayerPreviewColorChanged,
|
rgLayerPreviewColorChanged,
|
||||||
selectControlLayersSlice,
|
selectControlLayersSlice,
|
||||||
} from 'features/controlLayers/store/controlLayersSlice';
|
} from 'features/controlLayers/store/controlLayersSlice';
|
||||||
import { memo, useCallback, useMemo } from 'react';
|
import { memo, useCallback, useMemo } from 'react';
|
||||||
@ -33,7 +33,7 @@ export const RGLayerColorPicker = memo(({ layerId }: Props) => {
|
|||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const onColorChange = useCallback(
|
const onColorChange = useCallback(
|
||||||
(color: RgbColor) => {
|
(color: RgbColor) => {
|
||||||
dispatch(maskLayerPreviewColorChanged({ layerId, color }));
|
dispatch(rgLayerPreviewColorChanged({ layerId, color }));
|
||||||
},
|
},
|
||||||
[dispatch, layerId]
|
[dispatch, layerId]
|
||||||
);
|
);
|
||||||
|
@ -2,7 +2,7 @@ import { Box, Textarea } from '@invoke-ai/ui-library';
|
|||||||
import { useAppDispatch } from 'app/store/storeHooks';
|
import { useAppDispatch } from 'app/store/storeHooks';
|
||||||
import { RGLayerPromptDeleteButton } from 'features/controlLayers/components/RGLayer/RGLayerPromptDeleteButton';
|
import { RGLayerPromptDeleteButton } from 'features/controlLayers/components/RGLayer/RGLayerPromptDeleteButton';
|
||||||
import { useLayerNegativePrompt } from 'features/controlLayers/hooks/layerStateHooks';
|
import { useLayerNegativePrompt } from 'features/controlLayers/hooks/layerStateHooks';
|
||||||
import { maskLayerNegativePromptChanged } from 'features/controlLayers/store/controlLayersSlice';
|
import { rgLayerNegativePromptChanged } from 'features/controlLayers/store/controlLayersSlice';
|
||||||
import { PromptOverlayButtonWrapper } from 'features/parameters/components/Prompts/PromptOverlayButtonWrapper';
|
import { PromptOverlayButtonWrapper } from 'features/parameters/components/Prompts/PromptOverlayButtonWrapper';
|
||||||
import { AddPromptTriggerButton } from 'features/prompt/AddPromptTriggerButton';
|
import { AddPromptTriggerButton } from 'features/prompt/AddPromptTriggerButton';
|
||||||
import { PromptPopover } from 'features/prompt/PromptPopover';
|
import { PromptPopover } from 'features/prompt/PromptPopover';
|
||||||
@ -21,7 +21,7 @@ export const RGLayerNegativePrompt = memo(({ layerId }: Props) => {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const _onChange = useCallback(
|
const _onChange = useCallback(
|
||||||
(v: string) => {
|
(v: string) => {
|
||||||
dispatch(maskLayerNegativePromptChanged({ layerId, prompt: v }));
|
dispatch(rgLayerNegativePromptChanged({ layerId, prompt: v }));
|
||||||
},
|
},
|
||||||
[dispatch, layerId]
|
[dispatch, layerId]
|
||||||
);
|
);
|
||||||
|
@ -2,7 +2,7 @@ import { Box, Textarea } from '@invoke-ai/ui-library';
|
|||||||
import { useAppDispatch } from 'app/store/storeHooks';
|
import { useAppDispatch } from 'app/store/storeHooks';
|
||||||
import { RGLayerPromptDeleteButton } from 'features/controlLayers/components/RGLayer/RGLayerPromptDeleteButton';
|
import { RGLayerPromptDeleteButton } from 'features/controlLayers/components/RGLayer/RGLayerPromptDeleteButton';
|
||||||
import { useLayerPositivePrompt } from 'features/controlLayers/hooks/layerStateHooks';
|
import { useLayerPositivePrompt } from 'features/controlLayers/hooks/layerStateHooks';
|
||||||
import { maskLayerPositivePromptChanged } from 'features/controlLayers/store/controlLayersSlice';
|
import { rgLayerPositivePromptChanged } from 'features/controlLayers/store/controlLayersSlice';
|
||||||
import { PromptOverlayButtonWrapper } from 'features/parameters/components/Prompts/PromptOverlayButtonWrapper';
|
import { PromptOverlayButtonWrapper } from 'features/parameters/components/Prompts/PromptOverlayButtonWrapper';
|
||||||
import { AddPromptTriggerButton } from 'features/prompt/AddPromptTriggerButton';
|
import { AddPromptTriggerButton } from 'features/prompt/AddPromptTriggerButton';
|
||||||
import { PromptPopover } from 'features/prompt/PromptPopover';
|
import { PromptPopover } from 'features/prompt/PromptPopover';
|
||||||
@ -21,7 +21,7 @@ export const RGLayerPositivePrompt = memo(({ layerId }: Props) => {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const _onChange = useCallback(
|
const _onChange = useCallback(
|
||||||
(v: string) => {
|
(v: string) => {
|
||||||
dispatch(maskLayerPositivePromptChanged({ layerId, prompt: v }));
|
dispatch(rgLayerPositivePromptChanged({ layerId, prompt: v }));
|
||||||
},
|
},
|
||||||
[dispatch, layerId]
|
[dispatch, layerId]
|
||||||
);
|
);
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { IconButton, Tooltip } from '@invoke-ai/ui-library';
|
import { IconButton, Tooltip } from '@invoke-ai/ui-library';
|
||||||
import { useAppDispatch } from 'app/store/storeHooks';
|
import { useAppDispatch } from 'app/store/storeHooks';
|
||||||
import {
|
import {
|
||||||
maskLayerNegativePromptChanged,
|
rgLayerNegativePromptChanged,
|
||||||
maskLayerPositivePromptChanged,
|
rgLayerPositivePromptChanged,
|
||||||
} from 'features/controlLayers/store/controlLayersSlice';
|
} from 'features/controlLayers/store/controlLayersSlice';
|
||||||
import { memo, useCallback } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@ -18,9 +18,9 @@ export const RGLayerPromptDeleteButton = memo(({ layerId, polarity }: Props) =>
|
|||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const onClick = useCallback(() => {
|
const onClick = useCallback(() => {
|
||||||
if (polarity === 'positive') {
|
if (polarity === 'positive') {
|
||||||
dispatch(maskLayerPositivePromptChanged({ layerId, prompt: null }));
|
dispatch(rgLayerPositivePromptChanged({ layerId, prompt: null }));
|
||||||
} else {
|
} else {
|
||||||
dispatch(maskLayerNegativePromptChanged({ layerId, prompt: null }));
|
dispatch(rgLayerNegativePromptChanged({ layerId, prompt: null }));
|
||||||
}
|
}
|
||||||
}, [dispatch, layerId, polarity]);
|
}, [dispatch, layerId, polarity]);
|
||||||
return (
|
return (
|
||||||
|
@ -9,9 +9,9 @@ import {
|
|||||||
$lastMouseDownPos,
|
$lastMouseDownPos,
|
||||||
$tool,
|
$tool,
|
||||||
brushSizeChanged,
|
brushSizeChanged,
|
||||||
maskLayerLineAdded,
|
rfLayerLineAdded,
|
||||||
maskLayerPointsAdded,
|
rgLayerPointsAdded,
|
||||||
maskLayerRectAdded,
|
rgLayerRectAdded,
|
||||||
} from 'features/controlLayers/store/controlLayersSlice';
|
} from 'features/controlLayers/store/controlLayersSlice';
|
||||||
import type Konva from 'konva';
|
import type Konva from 'konva';
|
||||||
import type { KonvaEventObject } from 'konva/lib/Node';
|
import type { KonvaEventObject } from 'konva/lib/Node';
|
||||||
@ -71,7 +71,7 @@ export const useMouseEvents = () => {
|
|||||||
}
|
}
|
||||||
if (tool === 'brush' || tool === 'eraser') {
|
if (tool === 'brush' || tool === 'eraser') {
|
||||||
dispatch(
|
dispatch(
|
||||||
maskLayerLineAdded({
|
rfLayerLineAdded({
|
||||||
layerId: selectedLayerId,
|
layerId: selectedLayerId,
|
||||||
points: [pos.x, pos.y, pos.x, pos.y],
|
points: [pos.x, pos.y, pos.x, pos.y],
|
||||||
tool,
|
tool,
|
||||||
@ -94,7 +94,7 @@ export const useMouseEvents = () => {
|
|||||||
const tool = $tool.get();
|
const tool = $tool.get();
|
||||||
if (pos && lastPos && selectedLayerId && tool === 'rect') {
|
if (pos && lastPos && selectedLayerId && tool === 'rect') {
|
||||||
dispatch(
|
dispatch(
|
||||||
maskLayerRectAdded({
|
rgLayerRectAdded({
|
||||||
layerId: selectedLayerId,
|
layerId: selectedLayerId,
|
||||||
rect: {
|
rect: {
|
||||||
x: Math.min(pos.x, lastPos.x),
|
x: Math.min(pos.x, lastPos.x),
|
||||||
@ -128,7 +128,7 @@ export const useMouseEvents = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
lastCursorPosRef.current = [pos.x, pos.y];
|
lastCursorPosRef.current = [pos.x, pos.y];
|
||||||
dispatch(maskLayerPointsAdded({ layerId: selectedLayerId, point: lastCursorPosRef.current }));
|
dispatch(rgLayerPointsAdded({ layerId: selectedLayerId, point: lastCursorPosRef.current }));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[dispatch, selectedLayerId, tool]
|
[dispatch, selectedLayerId, tool]
|
||||||
@ -149,7 +149,7 @@ export const useMouseEvents = () => {
|
|||||||
$isMouseDown.get() &&
|
$isMouseDown.get() &&
|
||||||
(tool === 'brush' || tool === 'eraser')
|
(tool === 'brush' || tool === 'eraser')
|
||||||
) {
|
) {
|
||||||
dispatch(maskLayerPointsAdded({ layerId: selectedLayerId, point: [pos.x, pos.y] }));
|
dispatch(rgLayerPointsAdded({ layerId: selectedLayerId, point: [pos.x, pos.y] }));
|
||||||
}
|
}
|
||||||
$isMouseOver.set(false);
|
$isMouseOver.set(false);
|
||||||
$isMouseDown.set(false);
|
$isMouseDown.set(false);
|
||||||
@ -181,7 +181,7 @@ export const useMouseEvents = () => {
|
|||||||
}
|
}
|
||||||
if (tool === 'brush' || tool === 'eraser') {
|
if (tool === 'brush' || tool === 'eraser') {
|
||||||
dispatch(
|
dispatch(
|
||||||
maskLayerLineAdded({
|
rfLayerLineAdded({
|
||||||
layerId: selectedLayerId,
|
layerId: selectedLayerId,
|
||||||
points: [pos.x, pos.y, pos.x, pos.y],
|
points: [pos.x, pos.y, pos.x, pos.y],
|
||||||
tool,
|
tool,
|
||||||
|
@ -3,12 +3,17 @@ import { createSlice, isAnyOf } from '@reduxjs/toolkit';
|
|||||||
import type { PersistConfig, RootState } from 'app/store/store';
|
import type { PersistConfig, RootState } from 'app/store/store';
|
||||||
import { moveBackward, moveForward, moveToBack, moveToFront } from 'common/util/arrayUtils';
|
import { moveBackward, moveForward, moveToBack, moveToFront } from 'common/util/arrayUtils';
|
||||||
import { deepClone } from 'common/util/deepClone';
|
import { deepClone } from 'common/util/deepClone';
|
||||||
import { roundToMultiple } from 'common/util/roundDownToMultiple';
|
import type {
|
||||||
import {
|
CLIPVisionModel,
|
||||||
controlAdapterImageChanged,
|
ControlMode,
|
||||||
controlAdapterProcessedImageChanged,
|
ControlNetConfig,
|
||||||
isAnyControlAdapterAdded,
|
IPAdapterConfig,
|
||||||
} from 'features/controlAdapters/store/controlAdaptersSlice';
|
IPMethod,
|
||||||
|
ProcessorConfig,
|
||||||
|
T2IAdapterConfig,
|
||||||
|
} from 'features/controlLayers/util/controlAdapters';
|
||||||
|
import { buildControlAdapterProcessor, imageDTOToImageWithDims } from 'features/controlLayers/util/controlAdapters';
|
||||||
|
import { zModelIdentifierField } from 'features/nodes/types/common';
|
||||||
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';
|
||||||
@ -20,6 +25,7 @@ import { isEqual, partition } from 'lodash-es';
|
|||||||
import { atom } from 'nanostores';
|
import { atom } from 'nanostores';
|
||||||
import type { RgbColor } from 'react-colorful';
|
import type { RgbColor } from 'react-colorful';
|
||||||
import type { UndoableOptions } from 'redux-undo';
|
import type { UndoableOptions } from 'redux-undo';
|
||||||
|
import type { ControlNetModelConfig, ImageDTO, T2IAdapterModelConfig } from 'services/api/types';
|
||||||
import { assert } from 'tsafe';
|
import { assert } from 'tsafe';
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
|
||||||
@ -47,7 +53,6 @@ export const initialControlLayersState: ControlLayersState = {
|
|||||||
positivePrompt2: '',
|
positivePrompt2: '',
|
||||||
negativePrompt2: '',
|
negativePrompt2: '',
|
||||||
shouldConcatPrompts: true,
|
shouldConcatPrompts: true,
|
||||||
initialImage: null,
|
|
||||||
size: {
|
size: {
|
||||||
width: 512,
|
width: 512,
|
||||||
height: 512,
|
height: 512,
|
||||||
@ -82,76 +87,35 @@ const getVectorMaskPreviewColor = (state: ControlLayersState): RgbColor => {
|
|||||||
const lastColor = vmLayers[vmLayers.length - 1]?.previewColor;
|
const lastColor = vmLayers[vmLayers.length - 1]?.previewColor;
|
||||||
return LayerColors.next(lastColor);
|
return LayerColors.next(lastColor);
|
||||||
};
|
};
|
||||||
|
const getCALayer = (state: ControlLayersState, layerId: string): ControlAdapterLayer => {
|
||||||
|
const layer = state.layers.find((l) => l.id === layerId);
|
||||||
|
assert(isControlAdapterLayer(layer));
|
||||||
|
return layer;
|
||||||
|
};
|
||||||
|
const getIPALayer = (state: ControlLayersState, layerId: string): IPAdapterLayer => {
|
||||||
|
const layer = state.layers.find((l) => l.id === layerId);
|
||||||
|
assert(isIPAdapterLayer(layer));
|
||||||
|
return layer;
|
||||||
|
};
|
||||||
|
const getRGLayer = (state: ControlLayersState, layerId: string): RegionalGuidanceLayer => {
|
||||||
|
const layer = state.layers.find((l) => l.id === layerId);
|
||||||
|
assert(isRegionalGuidanceLayer(layer));
|
||||||
|
return layer;
|
||||||
|
};
|
||||||
|
const getRGLayerIPAdapter = (state: ControlLayersState, layerId: string, ipAdapterId: string): IPAdapterConfig => {
|
||||||
|
const layer = state.layers.find((l) => l.id === layerId);
|
||||||
|
assert(isRegionalGuidanceLayer(layer));
|
||||||
|
const ipAdapter = layer.ipAdapters.find((ipAdapter) => ipAdapter.id === ipAdapterId);
|
||||||
|
assert(ipAdapter);
|
||||||
|
return ipAdapter;
|
||||||
|
};
|
||||||
|
|
||||||
export const controlLayersSlice = createSlice({
|
export const controlLayersSlice = createSlice({
|
||||||
name: 'controlLayers',
|
name: 'controlLayers',
|
||||||
initialState: initialControlLayersState,
|
initialState: initialControlLayersState,
|
||||||
reducers: {
|
reducers: {
|
||||||
//#region All Layers
|
//#region All Layers
|
||||||
regionalGuidanceLayerAdded: (state, action: PayloadAction<{ layerId: string }>) => {
|
|
||||||
const { layerId } = action.payload;
|
|
||||||
const layer: RegionalGuidanceLayer = {
|
|
||||||
id: getRegionalGuidanceLayerId(layerId),
|
|
||||||
type: 'regional_guidance_layer',
|
|
||||||
isEnabled: true,
|
|
||||||
bbox: null,
|
|
||||||
bboxNeedsUpdate: false,
|
|
||||||
maskObjects: [],
|
|
||||||
previewColor: getVectorMaskPreviewColor(state),
|
|
||||||
x: 0,
|
|
||||||
y: 0,
|
|
||||||
autoNegative: 'invert',
|
|
||||||
needsPixelBbox: false,
|
|
||||||
positivePrompt: '',
|
|
||||||
negativePrompt: null,
|
|
||||||
ipAdapterIds: [],
|
|
||||||
isSelected: true,
|
|
||||||
};
|
|
||||||
state.layers.push(layer);
|
|
||||||
state.selectedLayerId = layer.id;
|
|
||||||
for (const layer of state.layers.filter(isRenderableLayer)) {
|
|
||||||
if (layer.id !== layerId) {
|
|
||||||
layer.isSelected = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
},
|
|
||||||
ipAdapterLayerAdded: (state, action: PayloadAction<{ layerId: string; ipAdapterId: string }>) => {
|
|
||||||
const { layerId, ipAdapterId } = action.payload;
|
|
||||||
const layer: IPAdapterLayer = {
|
|
||||||
id: getIPAdapterLayerId(layerId),
|
|
||||||
type: 'ip_adapter_layer',
|
|
||||||
isEnabled: true,
|
|
||||||
ipAdapterId,
|
|
||||||
};
|
|
||||||
state.layers.push(layer);
|
|
||||||
return;
|
|
||||||
},
|
|
||||||
controlAdapterLayerAdded: (state, action: PayloadAction<{ layerId: string; controlNetId: string }>) => {
|
|
||||||
const { layerId, controlNetId } = action.payload;
|
|
||||||
const layer: ControlAdapterLayer = {
|
|
||||||
id: getControlNetLayerId(layerId),
|
|
||||||
type: 'control_adapter_layer',
|
|
||||||
controlNetId,
|
|
||||||
x: 0,
|
|
||||||
y: 0,
|
|
||||||
bbox: null,
|
|
||||||
bboxNeedsUpdate: false,
|
|
||||||
isEnabled: true,
|
|
||||||
imageName: null,
|
|
||||||
opacity: 1,
|
|
||||||
isSelected: true,
|
|
||||||
isFilterEnabled: true,
|
|
||||||
};
|
|
||||||
state.layers.push(layer);
|
|
||||||
state.selectedLayerId = layer.id;
|
|
||||||
for (const layer of state.layers.filter(isRenderableLayer)) {
|
|
||||||
if (layer.id !== layerId) {
|
|
||||||
layer.isSelected = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
},
|
|
||||||
layerSelected: (state, action: PayloadAction<string>) => {
|
layerSelected: (state, action: PayloadAction<string>) => {
|
||||||
for (const layer of state.layers.filter(isRenderableLayer)) {
|
for (const layer of state.layers.filter(isRenderableLayer)) {
|
||||||
if (layer.id === action.payload) {
|
if (layer.id === action.payload) {
|
||||||
@ -245,7 +209,103 @@ export const controlLayersSlice = createSlice({
|
|||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
//#region CA Layers
|
//#region CA Layers
|
||||||
isFilterEnabledChanged: (state, action: PayloadAction<{ layerId: string; isFilterEnabled: boolean }>) => {
|
caLayerAdded: {
|
||||||
|
reducer: (
|
||||||
|
state,
|
||||||
|
action: PayloadAction<{ layerId: string; controlAdapter: ControlNetConfig | T2IAdapterConfig }>
|
||||||
|
) => {
|
||||||
|
const { layerId, controlAdapter } = action.payload;
|
||||||
|
const layer: ControlAdapterLayer = {
|
||||||
|
id: getCALayerId(layerId),
|
||||||
|
type: 'control_adapter_layer',
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
bbox: null,
|
||||||
|
bboxNeedsUpdate: false,
|
||||||
|
isEnabled: true,
|
||||||
|
opacity: 1,
|
||||||
|
isSelected: true,
|
||||||
|
isFilterEnabled: true,
|
||||||
|
controlAdapter,
|
||||||
|
};
|
||||||
|
state.layers.push(layer);
|
||||||
|
state.selectedLayerId = layer.id;
|
||||||
|
for (const layer of state.layers.filter(isRenderableLayer)) {
|
||||||
|
if (layer.id !== layerId) {
|
||||||
|
layer.isSelected = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
prepare: (controlAdapter: ControlNetConfig | T2IAdapterConfig) => ({
|
||||||
|
payload: { layerId: uuidv4(), controlAdapter },
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
caLayerImageChanged: (state, action: PayloadAction<{ layerId: string; imageDTO: ImageDTO | null }>) => {
|
||||||
|
const { layerId, imageDTO } = action.payload;
|
||||||
|
const layer = getCALayer(state, layerId);
|
||||||
|
layer.bbox = null;
|
||||||
|
layer.bboxNeedsUpdate = true;
|
||||||
|
layer.isEnabled = true;
|
||||||
|
layer.controlAdapter.image = imageDTO ? imageDTOToImageWithDims(imageDTO) : null;
|
||||||
|
},
|
||||||
|
caLayerProcessedImageChanged: (state, action: PayloadAction<{ layerId: string; imageDTO: ImageDTO | null }>) => {
|
||||||
|
const { layerId, imageDTO } = action.payload;
|
||||||
|
const layer = getCALayer(state, layerId);
|
||||||
|
layer.bbox = null;
|
||||||
|
layer.bboxNeedsUpdate = true;
|
||||||
|
layer.isEnabled = true;
|
||||||
|
layer.controlAdapter.processedImage = imageDTO ? imageDTOToImageWithDims(imageDTO) : null;
|
||||||
|
},
|
||||||
|
caLayerModelChanged: (
|
||||||
|
state,
|
||||||
|
action: PayloadAction<{
|
||||||
|
layerId: string;
|
||||||
|
modelConfig: ControlNetModelConfig | T2IAdapterModelConfig | null;
|
||||||
|
}>
|
||||||
|
) => {
|
||||||
|
const { layerId, modelConfig } = action.payload;
|
||||||
|
const layer = getCALayer(state, layerId);
|
||||||
|
if (!modelConfig) {
|
||||||
|
layer.controlAdapter.model = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
layer.controlAdapter.model = zModelIdentifierField.parse(modelConfig);
|
||||||
|
const candidateProcessorConfig = buildControlAdapterProcessor(modelConfig);
|
||||||
|
if (candidateProcessorConfig?.type !== layer.controlAdapter.processorConfig?.type) {
|
||||||
|
// The processor has changed. For example, the previous model was a Canny model and the new model is a Depth
|
||||||
|
// model. We need to use the new processor.
|
||||||
|
layer.controlAdapter.processedImage = null;
|
||||||
|
layer.controlAdapter.processorConfig = candidateProcessorConfig;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
caLayerWeightChanged: (state, action: PayloadAction<{ layerId: string; weight: number }>) => {
|
||||||
|
const { layerId, weight } = action.payload;
|
||||||
|
const layer = getCALayer(state, layerId);
|
||||||
|
layer.controlAdapter.weight = weight;
|
||||||
|
},
|
||||||
|
caLayerBeginEndStepPctChanged: (
|
||||||
|
state,
|
||||||
|
action: PayloadAction<{ layerId: string; beginEndStepPct: [number, number] }>
|
||||||
|
) => {
|
||||||
|
const { layerId, beginEndStepPct } = action.payload;
|
||||||
|
const layer = getCALayer(state, layerId);
|
||||||
|
layer.controlAdapter.beginEndStepPct = beginEndStepPct;
|
||||||
|
},
|
||||||
|
caLayerControlModeChanged: (state, action: PayloadAction<{ layerId: string; controlMode: ControlMode }>) => {
|
||||||
|
const { layerId, controlMode } = action.payload;
|
||||||
|
const layer = getCALayer(state, layerId);
|
||||||
|
assert(layer.controlAdapter.type === 'controlnet');
|
||||||
|
layer.controlAdapter.controlMode = controlMode;
|
||||||
|
},
|
||||||
|
caLayerProcessorConfigChanged: (
|
||||||
|
state,
|
||||||
|
action: PayloadAction<{ layerId: string; processorConfig: ProcessorConfig }>
|
||||||
|
) => {
|
||||||
|
const { layerId, processorConfig } = action.payload;
|
||||||
|
const layer = getCALayer(state, layerId);
|
||||||
|
layer.controlAdapter.processorConfig = processorConfig;
|
||||||
|
},
|
||||||
|
caLayerIsFilterEnabledChanged: (state, action: PayloadAction<{ layerId: string; isFilterEnabled: boolean }>) => {
|
||||||
const { layerId, isFilterEnabled } = action.payload;
|
const { layerId, isFilterEnabled } = action.payload;
|
||||||
const layer = state.layers.filter(isControlAdapterLayer).find((l) => l.id === layerId);
|
const layer = state.layers.filter(isControlAdapterLayer).find((l) => l.id === layerId);
|
||||||
if (layer) {
|
if (layer) {
|
||||||
@ -254,55 +314,119 @@ export const controlLayersSlice = createSlice({
|
|||||||
},
|
},
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
//#region Mask Layers
|
//#region IP Adapter Layers
|
||||||
maskLayerPositivePromptChanged: (state, action: PayloadAction<{ layerId: string; prompt: string | null }>) => {
|
ipaLayerAdded: {
|
||||||
|
reducer: (state, action: PayloadAction<{ layerId: string; ipAdapter: IPAdapterConfig }>) => {
|
||||||
|
const { layerId, ipAdapter } = action.payload;
|
||||||
|
const layer: IPAdapterLayer = {
|
||||||
|
id: getIPALayerId(layerId),
|
||||||
|
type: 'ip_adapter_layer',
|
||||||
|
isEnabled: true,
|
||||||
|
ipAdapter,
|
||||||
|
};
|
||||||
|
state.layers.push(layer);
|
||||||
|
},
|
||||||
|
prepare: (ipAdapter: IPAdapterConfig) => ({ payload: { layerId: uuidv4(), ipAdapter } }),
|
||||||
|
},
|
||||||
|
ipaLayerImageChanged: (state, action: PayloadAction<{ layerId: string; imageDTO: ImageDTO | null }>) => {
|
||||||
|
const { layerId, imageDTO } = action.payload;
|
||||||
|
const layer = getIPALayer(state, layerId);
|
||||||
|
layer.ipAdapter.image = imageDTO ? imageDTOToImageWithDims(imageDTO) : null;
|
||||||
|
},
|
||||||
|
ipaLayerWeightChanged: (state, action: PayloadAction<{ layerId: string; weight: number }>) => {
|
||||||
|
const { layerId, weight } = action.payload;
|
||||||
|
const layer = getIPALayer(state, layerId);
|
||||||
|
layer.ipAdapter.weight = weight;
|
||||||
|
},
|
||||||
|
ipaLayerBeginEndStepPctChanged: (
|
||||||
|
state,
|
||||||
|
action: PayloadAction<{ layerId: string; beginEndStepPct: [number, number] }>
|
||||||
|
) => {
|
||||||
|
const { layerId, beginEndStepPct } = action.payload;
|
||||||
|
const layer = getIPALayer(state, layerId);
|
||||||
|
layer.ipAdapter.beginEndStepPct = beginEndStepPct;
|
||||||
|
},
|
||||||
|
ipaLayerMethodChanged: (state, action: PayloadAction<{ layerId: string; method: IPMethod }>) => {
|
||||||
|
const { layerId, method } = action.payload;
|
||||||
|
const layer = getIPALayer(state, layerId);
|
||||||
|
layer.ipAdapter.method = method;
|
||||||
|
},
|
||||||
|
ipaLayerCLIPVisionModelChanged: (
|
||||||
|
state,
|
||||||
|
action: PayloadAction<{ layerId: string; clipVisionModel: CLIPVisionModel }>
|
||||||
|
) => {
|
||||||
|
const { layerId, clipVisionModel } = action.payload;
|
||||||
|
const layer = getIPALayer(state, layerId);
|
||||||
|
layer.ipAdapter.clipVisionModel = clipVisionModel;
|
||||||
|
},
|
||||||
|
//#endregion
|
||||||
|
|
||||||
|
//#region RG Layers
|
||||||
|
rgLayerAdded: (state, action: PayloadAction<{ layerId: string }>) => {
|
||||||
|
const { layerId } = action.payload;
|
||||||
|
const layer: RegionalGuidanceLayer = {
|
||||||
|
id: getRGLayerId(layerId),
|
||||||
|
type: 'regional_guidance_layer',
|
||||||
|
isEnabled: true,
|
||||||
|
bbox: null,
|
||||||
|
bboxNeedsUpdate: false,
|
||||||
|
maskObjects: [],
|
||||||
|
previewColor: getVectorMaskPreviewColor(state),
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
autoNegative: 'invert',
|
||||||
|
needsPixelBbox: false,
|
||||||
|
positivePrompt: '',
|
||||||
|
negativePrompt: null,
|
||||||
|
ipAdapters: [],
|
||||||
|
isSelected: true,
|
||||||
|
};
|
||||||
|
state.layers.push(layer);
|
||||||
|
state.selectedLayerId = layer.id;
|
||||||
|
for (const layer of state.layers.filter(isRenderableLayer)) {
|
||||||
|
if (layer.id !== layerId) {
|
||||||
|
layer.isSelected = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
rgLayerPositivePromptChanged: (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 = getRGLayer(state, layerId);
|
||||||
if (layer?.type === 'regional_guidance_layer') {
|
|
||||||
layer.positivePrompt = prompt;
|
layer.positivePrompt = prompt;
|
||||||
}
|
|
||||||
},
|
},
|
||||||
maskLayerNegativePromptChanged: (state, action: PayloadAction<{ layerId: string; prompt: string | null }>) => {
|
rgLayerNegativePromptChanged: (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 = getRGLayer(state, layerId);
|
||||||
if (layer?.type === 'regional_guidance_layer') {
|
|
||||||
layer.negativePrompt = prompt;
|
layer.negativePrompt = prompt;
|
||||||
}
|
|
||||||
},
|
},
|
||||||
maskLayerIPAdapterAdded: (state, action: PayloadAction<{ layerId: string; ipAdapterId: string }>) => {
|
rgLayerIPAdapterAdded: (state, action: PayloadAction<{ layerId: string; ipAdapter: IPAdapterConfig }>) => {
|
||||||
|
const { layerId, ipAdapter } = action.payload;
|
||||||
|
const layer = getRGLayer(state, layerId);
|
||||||
|
layer.ipAdapters.push(ipAdapter);
|
||||||
|
},
|
||||||
|
rgLayerIPAdapterDeleted: (state, action: PayloadAction<{ layerId: string; ipAdapterId: string }>) => {
|
||||||
const { layerId, ipAdapterId } = action.payload;
|
const { layerId, ipAdapterId } = action.payload;
|
||||||
const layer = state.layers.find((l) => l.id === layerId);
|
const layer = getRGLayer(state, layerId);
|
||||||
if (layer?.type === 'regional_guidance_layer') {
|
layer.ipAdapters = layer.ipAdapters.filter((ipAdapter) => ipAdapter.id !== ipAdapterId);
|
||||||
layer.ipAdapterIds.push(ipAdapterId);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
maskLayerIPAdapterDeleted: (state, action: PayloadAction<{ layerId: string; ipAdapterId: string }>) => {
|
rgLayerPreviewColorChanged: (state, action: PayloadAction<{ layerId: string; color: RgbColor }>) => {
|
||||||
const { layerId, ipAdapterId } = action.payload;
|
|
||||||
const layer = state.layers.find((l) => l.id === layerId);
|
|
||||||
if (layer?.type === 'regional_guidance_layer') {
|
|
||||||
layer.ipAdapterIds = layer.ipAdapterIds.filter((id) => id !== ipAdapterId);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
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 = getRGLayer(state, layerId);
|
||||||
if (layer?.type === 'regional_guidance_layer') {
|
|
||||||
layer.previewColor = color;
|
layer.previewColor = color;
|
||||||
}
|
|
||||||
},
|
},
|
||||||
maskLayerLineAdded: {
|
rgLayerLineAdded: {
|
||||||
reducer: (
|
reducer: (
|
||||||
state,
|
state,
|
||||||
action: PayloadAction<
|
action: PayloadAction<{
|
||||||
{ layerId: string; points: [number, number, number, number]; tool: DrawingTool },
|
layerId: string;
|
||||||
string,
|
points: [number, number, number, number];
|
||||||
{ uuid: string }
|
tool: DrawingTool;
|
||||||
>
|
lineUuid: string;
|
||||||
|
}>
|
||||||
) => {
|
) => {
|
||||||
const { layerId, points, tool } = action.payload;
|
const { layerId, points, tool, lineUuid } = action.payload;
|
||||||
const layer = state.layers.find((l) => l.id === layerId);
|
const layer = getRGLayer(state, layerId);
|
||||||
if (layer?.type === 'regional_guidance_layer') {
|
const lineId = getRGLayerLineId(layer.id, lineUuid);
|
||||||
const lineId = getRegionalGuidanceLayerLineId(layer.id, action.meta.uuid);
|
|
||||||
layer.maskObjects.push({
|
layer.maskObjects.push({
|
||||||
type: 'vector_mask_line',
|
type: 'vector_mask_line',
|
||||||
tool: tool,
|
tool: tool,
|
||||||
@ -316,17 +440,14 @@ export const controlLayersSlice = createSlice({
|
|||||||
if (!layer.needsPixelBbox && tool === 'eraser') {
|
if (!layer.needsPixelBbox && tool === 'eraser') {
|
||||||
layer.needsPixelBbox = true;
|
layer.needsPixelBbox = true;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
},
|
},
|
||||||
prepare: (payload: { layerId: string; points: [number, number, number, number]; tool: DrawingTool }) => ({
|
prepare: (payload: { layerId: string; points: [number, number, number, number]; tool: DrawingTool }) => ({
|
||||||
payload,
|
payload: { ...payload, lineUuid: uuidv4() },
|
||||||
meta: { uuid: uuidv4() },
|
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
maskLayerPointsAdded: (state, action: PayloadAction<{ layerId: string; point: [number, number] }>) => {
|
rgLayerPointsAdded: (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 = getRGLayer(state, layerId);
|
||||||
if (layer?.type === 'regional_guidance_layer') {
|
|
||||||
const lastLine = layer.maskObjects.findLast(isLine);
|
const lastLine = layer.maskObjects.findLast(isLine);
|
||||||
if (!lastLine) {
|
if (!lastLine) {
|
||||||
return;
|
return;
|
||||||
@ -335,18 +456,16 @@ export const controlLayersSlice = createSlice({
|
|||||||
// TODO: Handle this in the event listener
|
// TODO: Handle this in the event listener
|
||||||
lastLine.points.push(point[0] - layer.x, point[1] - layer.y);
|
lastLine.points.push(point[0] - layer.x, point[1] - layer.y);
|
||||||
layer.bboxNeedsUpdate = true;
|
layer.bboxNeedsUpdate = true;
|
||||||
}
|
|
||||||
},
|
},
|
||||||
maskLayerRectAdded: {
|
rgLayerRectAdded: {
|
||||||
reducer: (state, action: PayloadAction<{ layerId: string; rect: IRect }, string, { uuid: string }>) => {
|
reducer: (state, action: PayloadAction<{ layerId: string; rect: IRect; rectUuid: string }>) => {
|
||||||
const { layerId, rect } = action.payload;
|
const { layerId, rect, rectUuid } = action.payload;
|
||||||
if (rect.height === 0 || rect.width === 0) {
|
if (rect.height === 0 || rect.width === 0) {
|
||||||
// Ignore zero-area rectangles
|
// Ignore zero-area rectangles
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const layer = state.layers.find((l) => l.id === layerId);
|
const layer = getRGLayer(state, layerId);
|
||||||
if (layer?.type === 'regional_guidance_layer') {
|
const id = getRGLayerRectId(layer.id, rectUuid);
|
||||||
const id = getMaskedGuidnaceLayerRectId(layer.id, action.meta.uuid);
|
|
||||||
layer.maskObjects.push({
|
layer.maskObjects.push({
|
||||||
type: 'vector_mask_rect',
|
type: 'vector_mask_rect',
|
||||||
id,
|
id,
|
||||||
@ -356,19 +475,56 @@ export const controlLayersSlice = createSlice({
|
|||||||
height: rect.height,
|
height: rect.height,
|
||||||
});
|
});
|
||||||
layer.bboxNeedsUpdate = true;
|
layer.bboxNeedsUpdate = true;
|
||||||
}
|
|
||||||
},
|
},
|
||||||
prepare: (payload: { layerId: string; rect: IRect }) => ({ payload, meta: { uuid: uuidv4() } }),
|
prepare: (payload: { layerId: string; rect: IRect }) => ({ payload: { ...payload, rectUuid: uuidv4() } }),
|
||||||
},
|
},
|
||||||
maskLayerAutoNegativeChanged: (
|
rgLayerAutoNegativeChanged: (
|
||||||
state,
|
state,
|
||||||
action: PayloadAction<{ layerId: string; autoNegative: ParameterAutoNegative }>
|
action: PayloadAction<{ layerId: string; autoNegative: ParameterAutoNegative }>
|
||||||
) => {
|
) => {
|
||||||
const { layerId, autoNegative } = action.payload;
|
const { layerId, autoNegative } = action.payload;
|
||||||
const layer = state.layers.find((l) => l.id === layerId);
|
const layer = getRGLayer(state, layerId);
|
||||||
if (layer?.type === 'regional_guidance_layer') {
|
|
||||||
layer.autoNegative = autoNegative;
|
layer.autoNegative = autoNegative;
|
||||||
}
|
},
|
||||||
|
rgLayerIPAdapterImageChanged: (
|
||||||
|
state,
|
||||||
|
action: PayloadAction<{ layerId: string; ipAdapterId: string; imageDTO: ImageDTO | null }>
|
||||||
|
) => {
|
||||||
|
const { layerId, ipAdapterId, imageDTO } = action.payload;
|
||||||
|
const ipAdapter = getRGLayerIPAdapter(state, layerId, ipAdapterId);
|
||||||
|
ipAdapter.image = imageDTO ? imageDTOToImageWithDims(imageDTO) : null;
|
||||||
|
},
|
||||||
|
rgLayerIPAdapterWeightChanged: (
|
||||||
|
state,
|
||||||
|
action: PayloadAction<{ layerId: string; ipAdapterId: string; weight: number }>
|
||||||
|
) => {
|
||||||
|
const { layerId, ipAdapterId, weight } = action.payload;
|
||||||
|
const ipAdapter = getRGLayerIPAdapter(state, layerId, ipAdapterId);
|
||||||
|
ipAdapter.weight = weight;
|
||||||
|
},
|
||||||
|
rgLayerIPAdapterBeginEndStepPctChanged: (
|
||||||
|
state,
|
||||||
|
action: PayloadAction<{ layerId: string; ipAdapterId: string; beginEndStepPct: [number, number] }>
|
||||||
|
) => {
|
||||||
|
const { layerId, ipAdapterId, beginEndStepPct } = action.payload;
|
||||||
|
const ipAdapter = getRGLayerIPAdapter(state, layerId, ipAdapterId);
|
||||||
|
ipAdapter.beginEndStepPct = beginEndStepPct;
|
||||||
|
},
|
||||||
|
rgLayerIPAdapterMethodChanged: (
|
||||||
|
state,
|
||||||
|
action: PayloadAction<{ layerId: string; ipAdapterId: string; method: IPMethod }>
|
||||||
|
) => {
|
||||||
|
const { layerId, ipAdapterId, method } = action.payload;
|
||||||
|
const ipAdapter = getRGLayerIPAdapter(state, layerId, ipAdapterId);
|
||||||
|
ipAdapter.method = method;
|
||||||
|
},
|
||||||
|
rgLayerIPAdapterCLIPVisionModelChanged: (
|
||||||
|
state,
|
||||||
|
action: PayloadAction<{ layerId: string; ipAdapterId: string; clipVisionModel: CLIPVisionModel }>
|
||||||
|
) => {
|
||||||
|
const { layerId, ipAdapterId, clipVisionModel } = action.payload;
|
||||||
|
const ipAdapter = getRGLayerIPAdapter(state, layerId, ipAdapterId);
|
||||||
|
ipAdapter.clipVisionModel = clipVisionModel;
|
||||||
},
|
},
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
@ -451,36 +607,14 @@ export const controlLayersSlice = createSlice({
|
|||||||
state.size.height = height;
|
state.size.height = height;
|
||||||
});
|
});
|
||||||
|
|
||||||
builder.addCase(controlAdapterImageChanged, (state, action) => {
|
// // TODO: This is a temp fix to reduce issues with T2I adapter having a different downscaling
|
||||||
const { id, controlImage } = action.payload;
|
// // factor than the UNet. Hopefully we get an upstream fix in diffusers.
|
||||||
const layer = state.layers.filter(isControlAdapterLayer).find((l) => l.controlNetId === id);
|
// builder.addMatcher(isAnyControlAdapterAdded, (state, action) => {
|
||||||
if (layer) {
|
// if (action.payload.type === 't2i_adapter') {
|
||||||
layer.bbox = null;
|
// state.size.width = roundToMultiple(state.size.width, 64);
|
||||||
layer.bboxNeedsUpdate = true;
|
// state.size.height = roundToMultiple(state.size.height, 64);
|
||||||
layer.isEnabled = true;
|
// }
|
||||||
layer.imageName = controlImage?.image_name ?? null;
|
// });
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
builder.addCase(controlAdapterProcessedImageChanged, (state, action) => {
|
|
||||||
const { id, processedControlImage } = action.payload;
|
|
||||||
const layer = state.layers.filter(isControlAdapterLayer).find((l) => l.controlNetId === id);
|
|
||||||
if (layer) {
|
|
||||||
layer.bbox = null;
|
|
||||||
layer.bboxNeedsUpdate = true;
|
|
||||||
layer.isEnabled = true;
|
|
||||||
layer.imageName = processedControlImage?.image_name ?? null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// TODO: This is a temp fix to reduce issues with T2I adapter having a different downscaling
|
|
||||||
// factor than the UNet. Hopefully we get an upstream fix in diffusers.
|
|
||||||
builder.addMatcher(isAnyControlAdapterAdded, (state, action) => {
|
|
||||||
if (action.payload.type === 't2i_adapter') {
|
|
||||||
state.size.width = roundToMultiple(state.size.width, 64);
|
|
||||||
state.size.height = roundToMultiple(state.size.height, 64);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -529,22 +663,22 @@ export const {
|
|||||||
layerVisibilityToggled,
|
layerVisibilityToggled,
|
||||||
selectedLayerReset,
|
selectedLayerReset,
|
||||||
selectedLayerDeleted,
|
selectedLayerDeleted,
|
||||||
regionalGuidanceLayerAdded,
|
rgLayerAdded: regionalGuidanceLayerAdded,
|
||||||
ipAdapterLayerAdded,
|
ipaLayerAdded: ipAdapterLayerAdded,
|
||||||
controlAdapterLayerAdded,
|
caLayerAdded: controlAdapterLayerAdded,
|
||||||
layerOpacityChanged,
|
layerOpacityChanged,
|
||||||
// CA layer actions
|
// CA layer actions
|
||||||
isFilterEnabledChanged,
|
caLayerIsFilterEnabledChanged: isFilterEnabledChanged,
|
||||||
// Mask layer actions
|
// Mask layer actions
|
||||||
maskLayerLineAdded,
|
rgLayerLineAdded: maskLayerLineAdded,
|
||||||
maskLayerPointsAdded,
|
rgLayerPointsAdded: maskLayerPointsAdded,
|
||||||
maskLayerRectAdded,
|
rgLayerRectAdded: maskLayerRectAdded,
|
||||||
maskLayerNegativePromptChanged,
|
rgLayerNegativePromptChanged: maskLayerNegativePromptChanged,
|
||||||
maskLayerPositivePromptChanged,
|
rgLayerPositivePromptChanged: maskLayerPositivePromptChanged,
|
||||||
maskLayerIPAdapterAdded,
|
rgLayerIPAdapterAdded: maskLayerIPAdapterAdded,
|
||||||
maskLayerIPAdapterDeleted,
|
rgLayerIPAdapterDeleted: maskLayerIPAdapterDeleted,
|
||||||
maskLayerAutoNegativeChanged,
|
rgLayerAutoNegativeChanged: maskLayerAutoNegativeChanged,
|
||||||
maskLayerPreviewColorChanged,
|
rgLayerPreviewColorChanged: maskLayerPreviewColorChanged,
|
||||||
// Base layer actions
|
// Base layer actions
|
||||||
positivePromptChanged,
|
positivePromptChanged,
|
||||||
negativePromptChanged,
|
negativePromptChanged,
|
||||||
@ -561,20 +695,6 @@ export const {
|
|||||||
redo,
|
redo,
|
||||||
} = controlLayersSlice.actions;
|
} = controlLayersSlice.actions;
|
||||||
|
|
||||||
export const selectAllControlAdapterIds = (controlLayers: ControlLayersState) =>
|
|
||||||
controlLayers.layers.flatMap((l) => {
|
|
||||||
if (l.type === 'control_adapter_layer') {
|
|
||||||
return [l.controlNetId];
|
|
||||||
}
|
|
||||||
if (l.type === 'ip_adapter_layer') {
|
|
||||||
return [l.ipAdapterId];
|
|
||||||
}
|
|
||||||
if (l.type === 'regional_guidance_layer') {
|
|
||||||
return l.ipAdapterIds;
|
|
||||||
}
|
|
||||||
return [];
|
|
||||||
});
|
|
||||||
|
|
||||||
export const selectControlLayersSlice = (state: RootState) => state.controlLayers;
|
export const selectControlLayersSlice = (state: RootState) => state.controlLayers;
|
||||||
|
|
||||||
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
|
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
|
||||||
@ -600,24 +720,23 @@ export const BACKGROUND_RECT_ID = 'background_layer.rect';
|
|||||||
export const NO_LAYERS_MESSAGE_LAYER_ID = 'no_layers_message';
|
export const NO_LAYERS_MESSAGE_LAYER_ID = 'no_layers_message';
|
||||||
|
|
||||||
// Names (aka classes) for Konva layers and objects
|
// Names (aka classes) for Konva layers and objects
|
||||||
export const CONTROLNET_LAYER_NAME = 'control_adapter_layer';
|
export const CA_LAYER_NAME = 'control_adapter_layer';
|
||||||
export const CONTROLNET_LAYER_IMAGE_NAME = 'control_adapter_layer.image';
|
export const CA_LAYER_IMAGE_NAME = 'control_adapter_layer.image';
|
||||||
export const regional_guidance_layer_NAME = 'regional_guidance_layer';
|
export const RG_LAYER_NAME = 'regional_guidance_layer';
|
||||||
export const regional_guidance_layer_LINE_NAME = 'regional_guidance_layer.line';
|
export const RG_LAYER_LINE_NAME = 'regional_guidance_layer.line';
|
||||||
export const regional_guidance_layer_OBJECT_GROUP_NAME = 'regional_guidance_layer.object_group';
|
export const RG_LAYER_OBJECT_GROUP_NAME = 'regional_guidance_layer.object_group';
|
||||||
export const regional_guidance_layer_RECT_NAME = 'regional_guidance_layer.rect';
|
export const RG_LAYER_RECT_NAME = 'regional_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 getRegionalGuidanceLayerId = (layerId: string) => `${regional_guidance_layer_NAME}_${layerId}`;
|
const getRGLayerId = (layerId: string) => `${RG_LAYER_NAME}_${layerId}`;
|
||||||
const getRegionalGuidanceLayerLineId = (layerId: string, lineId: string) => `${layerId}.line_${lineId}`;
|
const getRGLayerLineId = (layerId: string, lineId: string) => `${layerId}.line_${lineId}`;
|
||||||
const getMaskedGuidnaceLayerRectId = (layerId: string, lineId: string) => `${layerId}.rect_${lineId}`;
|
const getRGLayerRectId = (layerId: string, lineId: string) => `${layerId}.rect_${lineId}`;
|
||||||
export const getRegionalGuidanceLayerObjectGroupId = (layerId: string, groupId: string) =>
|
export const getRGLayerObjectGroupId = (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 getControlNetLayerId = (layerId: string) => `control_adapter_layer_${layerId}`;
|
const getCALayerId = (layerId: string) => `control_adapter_layer_${layerId}`;
|
||||||
export const getControlNetLayerImageId = (layerId: string, imageName: string) => `${layerId}.image_${imageName}`;
|
export const getCALayerImageId = (layerId: string, imageName: string) => `${layerId}.image_${imageName}`;
|
||||||
const getIPAdapterLayerId = (layerId: string) => `ip_adapter_layer_${layerId}`;
|
const getIPALayerId = (layerId: string) => `ip_adapter_layer_${layerId}`;
|
||||||
|
|
||||||
export const controlLayersPersistConfig: PersistConfig<ControlLayersState> = {
|
export const controlLayersPersistConfig: PersistConfig<ControlLayersState> = {
|
||||||
name: controlLayersSlice.name,
|
name: controlLayersSlice.name,
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import type { ControlNetConfig, IPAdapterConfig,T2IAdapterConfig } from 'features/controlLayers/util/controlAdapters';
|
||||||
import type { AspectRatioState } from 'features/parameters/components/ImageSize/types';
|
import type { AspectRatioState } from 'features/parameters/components/ImageSize/types';
|
||||||
import type {
|
import type {
|
||||||
ParameterAutoNegative,
|
ParameterAutoNegative,
|
||||||
@ -47,15 +48,14 @@ type RenderableLayerBase = LayerBase & {
|
|||||||
|
|
||||||
export type ControlAdapterLayer = RenderableLayerBase & {
|
export type ControlAdapterLayer = RenderableLayerBase & {
|
||||||
type: 'control_adapter_layer'; // technically, also t2i adapter layer
|
type: 'control_adapter_layer'; // technically, also t2i adapter layer
|
||||||
controlNetId: string;
|
|
||||||
imageName: string | null;
|
|
||||||
opacity: number;
|
opacity: number;
|
||||||
isFilterEnabled: boolean;
|
isFilterEnabled: boolean;
|
||||||
|
controlAdapter: ControlNetConfig | T2IAdapterConfig;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type IPAdapterLayer = LayerBase & {
|
export type IPAdapterLayer = LayerBase & {
|
||||||
type: 'ip_adapter_layer'; // technically, also t2i adapter layer
|
type: 'ip_adapter_layer';
|
||||||
ipAdapterId: string;
|
ipAdapter: IPAdapterConfig;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type RegionalGuidanceLayer = RenderableLayerBase & {
|
export type RegionalGuidanceLayer = RenderableLayerBase & {
|
||||||
@ -63,7 +63,7 @@ export type RegionalGuidanceLayer = RenderableLayerBase & {
|
|||||||
maskObjects: (VectorMaskLine | VectorMaskRect)[];
|
maskObjects: (VectorMaskLine | VectorMaskRect)[];
|
||||||
positivePrompt: ParameterPositivePrompt | null;
|
positivePrompt: ParameterPositivePrompt | null;
|
||||||
negativePrompt: ParameterNegativePrompt | null; // Up to one text prompt per mask
|
negativePrompt: ParameterNegativePrompt | null; // Up to one text prompt per mask
|
||||||
ipAdapterIds: string[]; // Any number of image prompts
|
ipAdapters: IPAdapterConfig[]; // 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
|
||||||
@ -83,7 +83,6 @@ export type ControlLayersState = {
|
|||||||
positivePrompt2: ParameterPositiveStylePromptSDXL;
|
positivePrompt2: ParameterPositiveStylePromptSDXL;
|
||||||
negativePrompt2: ParameterNegativeStylePromptSDXL;
|
negativePrompt2: ParameterNegativeStylePromptSDXL;
|
||||||
shouldConcatPrompts: boolean;
|
shouldConcatPrompts: boolean;
|
||||||
initialImage: string | null;
|
|
||||||
size: {
|
size: {
|
||||||
width: ParameterWidth;
|
width: ParameterWidth;
|
||||||
height: ParameterHeight;
|
height: ParameterHeight;
|
||||||
|
@ -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 { regional_guidance_layer_OBJECT_GROUP_NAME } from 'features/controlLayers/store/controlLayersSlice';
|
import { RG_LAYER_OBJECT_GROUP_NAME } from 'features/controlLayers/store/controlLayersSlice';
|
||||||
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() === regional_guidance_layer_OBJECT_GROUP_NAME) {
|
if (child.name() === RG_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();
|
||||||
|
@ -0,0 +1,354 @@
|
|||||||
|
import { deepClone } from 'common/util/deepClone';
|
||||||
|
import type {
|
||||||
|
ParameterControlNetModel,
|
||||||
|
ParameterIPAdapterModel,
|
||||||
|
ParameterT2IAdapterModel,
|
||||||
|
} from 'features/parameters/types/parameterSchemas';
|
||||||
|
import { merge } from 'lodash-es';
|
||||||
|
import type {
|
||||||
|
BaseModelType,
|
||||||
|
CannyImageProcessorInvocation,
|
||||||
|
ColorMapImageProcessorInvocation,
|
||||||
|
ContentShuffleImageProcessorInvocation,
|
||||||
|
ControlNetInvocation,
|
||||||
|
ControlNetModelConfig,
|
||||||
|
DepthAnythingImageProcessorInvocation,
|
||||||
|
DWOpenposeImageProcessorInvocation,
|
||||||
|
HedImageProcessorInvocation,
|
||||||
|
ImageDTO,
|
||||||
|
LineartAnimeImageProcessorInvocation,
|
||||||
|
LineartImageProcessorInvocation,
|
||||||
|
MediapipeFaceProcessorInvocation,
|
||||||
|
MidasDepthImageProcessorInvocation,
|
||||||
|
MlsdImageProcessorInvocation,
|
||||||
|
NormalbaeImageProcessorInvocation,
|
||||||
|
PidiImageProcessorInvocation,
|
||||||
|
T2IAdapterModelConfig,
|
||||||
|
ZoeDepthImageProcessorInvocation,
|
||||||
|
} from 'services/api/types';
|
||||||
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
const zDepthAnythingModelSize = z.enum(['large', 'base', 'small']);
|
||||||
|
export type DepthAnythingModelSize = z.infer<typeof zDepthAnythingModelSize>;
|
||||||
|
export const isDepthAnythingModelSize = (v: unknown): v is DepthAnythingModelSize =>
|
||||||
|
zDepthAnythingModelSize.safeParse(v).success;
|
||||||
|
|
||||||
|
export type CannyProcessorConfig = Required<
|
||||||
|
Pick<CannyImageProcessorInvocation, 'type' | 'low_threshold' | 'high_threshold'>
|
||||||
|
>;
|
||||||
|
export type ColorMapProcessorConfig = Required<Pick<ColorMapImageProcessorInvocation, 'type' | 'color_map_tile_size'>>;
|
||||||
|
export type ContentShuffleProcessorConfig = Required<
|
||||||
|
Pick<ContentShuffleImageProcessorInvocation, 'type' | 'w' | 'h' | 'f'>
|
||||||
|
>;
|
||||||
|
export type DepthAnythingProcessorConfig = Required<Pick<DepthAnythingImageProcessorInvocation, 'type' | 'model_size'>>;
|
||||||
|
export type HedProcessorConfig = Required<Pick<HedImageProcessorInvocation, 'type' | 'scribble'>>;
|
||||||
|
export type LineartAnimeProcessorConfig = Required<Pick<LineartAnimeImageProcessorInvocation, 'type'>>;
|
||||||
|
export type LineartProcessorConfig = Required<Pick<LineartImageProcessorInvocation, 'type' | 'coarse'>>;
|
||||||
|
export type MediapipeFaceProcessorConfig = Required<
|
||||||
|
Pick<MediapipeFaceProcessorInvocation, 'type' | 'max_faces' | 'min_confidence'>
|
||||||
|
>;
|
||||||
|
export type MidasDepthProcessorConfig = Required<Pick<MidasDepthImageProcessorInvocation, 'type' | 'a_mult' | 'bg_th'>>;
|
||||||
|
export type MlsdProcessorConfig = Required<Pick<MlsdImageProcessorInvocation, 'type' | 'thr_v' | 'thr_d'>>;
|
||||||
|
export type NormalbaeProcessorConfig = Required<Pick<NormalbaeImageProcessorInvocation, 'type'>>;
|
||||||
|
export type DWOpenposeProcessorConfig = Required<
|
||||||
|
Pick<DWOpenposeImageProcessorInvocation, 'type' | 'draw_body' | 'draw_face' | 'draw_hands'>
|
||||||
|
>;
|
||||||
|
export type PidiProcessorConfig = Required<Pick<PidiImageProcessorInvocation, 'type' | 'safe' | 'scribble'>>;
|
||||||
|
export type ZoeDepthProcessorConfig = Required<Pick<ZoeDepthImageProcessorInvocation, 'type'>>;
|
||||||
|
|
||||||
|
export type ProcessorConfig =
|
||||||
|
| CannyProcessorConfig
|
||||||
|
| ColorMapProcessorConfig
|
||||||
|
| ContentShuffleProcessorConfig
|
||||||
|
| DepthAnythingProcessorConfig
|
||||||
|
| HedProcessorConfig
|
||||||
|
| LineartAnimeProcessorConfig
|
||||||
|
| LineartProcessorConfig
|
||||||
|
| MediapipeFaceProcessorConfig
|
||||||
|
| MidasDepthProcessorConfig
|
||||||
|
| MlsdProcessorConfig
|
||||||
|
| NormalbaeProcessorConfig
|
||||||
|
| DWOpenposeProcessorConfig
|
||||||
|
| PidiProcessorConfig
|
||||||
|
| ZoeDepthProcessorConfig;
|
||||||
|
|
||||||
|
type ImageWithDims = {
|
||||||
|
imageName: string;
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
type ControlAdapterBase = {
|
||||||
|
id: string;
|
||||||
|
isEnabled: boolean;
|
||||||
|
weight: number;
|
||||||
|
image: ImageWithDims | null;
|
||||||
|
processedImage: ImageWithDims | null;
|
||||||
|
processorConfig: ProcessorConfig | null;
|
||||||
|
beginEndStepPct: [number, number];
|
||||||
|
};
|
||||||
|
export type ControlMode = NonNullable<ControlNetInvocation['control_mode']>;
|
||||||
|
|
||||||
|
export type ControlNetConfig = ControlAdapterBase & {
|
||||||
|
type: 'controlnet';
|
||||||
|
model: ParameterControlNetModel | null;
|
||||||
|
controlMode: ControlMode;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type T2IAdapterConfig = ControlAdapterBase & {
|
||||||
|
type: 't2i_adapter';
|
||||||
|
model: ParameterT2IAdapterModel | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type CLIPVisionModel = 'ViT-H' | 'ViT-G';
|
||||||
|
|
||||||
|
const zIPMethod = z.enum(['full', 'style', 'composition']);
|
||||||
|
|
||||||
|
export type IPMethod = z.infer<typeof zIPMethod>;
|
||||||
|
export const isIPMethod = (v: unknown): v is IPMethod => zIPMethod.safeParse(v).success;
|
||||||
|
|
||||||
|
export type IPAdapterConfig = {
|
||||||
|
id: string;
|
||||||
|
type: 'ip_adapter';
|
||||||
|
isEnabled: boolean;
|
||||||
|
weight: number;
|
||||||
|
method: IPMethod;
|
||||||
|
image: ImageWithDims | null;
|
||||||
|
model: ParameterIPAdapterModel | null;
|
||||||
|
clipVisionModel: CLIPVisionModel;
|
||||||
|
beginEndStepPct: [number, number];
|
||||||
|
};
|
||||||
|
|
||||||
|
type ProcessorData<T extends ProcessorConfig['type']> = {
|
||||||
|
labelTKey: string;
|
||||||
|
descriptionTKey: string;
|
||||||
|
buildDefaults(baseModel?: BaseModelType): Extract<ProcessorConfig, { type: T }>;
|
||||||
|
};
|
||||||
|
|
||||||
|
type ControlNetProcessorsDict = {
|
||||||
|
[key in ProcessorConfig['type']]: ProcessorData<key>;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* A dict of ControlNet processors, including:
|
||||||
|
* - label translation key
|
||||||
|
* - description translation key
|
||||||
|
* - a builder to create default values for the config
|
||||||
|
*
|
||||||
|
* TODO: Generate from the OpenAPI schema
|
||||||
|
*/
|
||||||
|
export const CONTROLNET_PROCESSORS: ControlNetProcessorsDict = {
|
||||||
|
canny_image_processor: {
|
||||||
|
labelTKey: 'controlnet.canny',
|
||||||
|
descriptionTKey: 'controlnet.cannyDescription',
|
||||||
|
buildDefaults: () => ({
|
||||||
|
id: `canny_image_processor_${uuidv4()}`,
|
||||||
|
type: 'canny_image_processor',
|
||||||
|
low_threshold: 100,
|
||||||
|
high_threshold: 200,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
color_map_image_processor: {
|
||||||
|
labelTKey: 'controlnet.colorMap',
|
||||||
|
descriptionTKey: 'controlnet.colorMapDescription',
|
||||||
|
buildDefaults: () => ({
|
||||||
|
id: `color_map_image_processor_${uuidv4()}`,
|
||||||
|
type: 'color_map_image_processor',
|
||||||
|
color_map_tile_size: 64,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
content_shuffle_image_processor: {
|
||||||
|
labelTKey: 'controlnet.contentShuffle',
|
||||||
|
descriptionTKey: 'controlnet.contentShuffleDescription',
|
||||||
|
buildDefaults: (baseModel) => ({
|
||||||
|
id: `content_shuffle_image_processor_${uuidv4()}`,
|
||||||
|
type: 'content_shuffle_image_processor',
|
||||||
|
h: baseModel === 'sdxl' ? 1024 : 512,
|
||||||
|
w: baseModel === 'sdxl' ? 1024 : 512,
|
||||||
|
f: baseModel === 'sdxl' ? 512 : 256,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
depth_anything_image_processor: {
|
||||||
|
labelTKey: 'controlnet.depthAnything',
|
||||||
|
descriptionTKey: 'controlnet.depthAnythingDescription',
|
||||||
|
buildDefaults: () => ({
|
||||||
|
id: `depth_anything_image_processor_${uuidv4()}`,
|
||||||
|
type: 'depth_anything_image_processor',
|
||||||
|
model_size: 'small',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
hed_image_processor: {
|
||||||
|
labelTKey: 'controlnet.hed',
|
||||||
|
descriptionTKey: 'controlnet.hedDescription',
|
||||||
|
buildDefaults: () => ({
|
||||||
|
id: `hed_image_processor_${uuidv4()}`,
|
||||||
|
type: 'hed_image_processor',
|
||||||
|
scribble: false,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
lineart_anime_image_processor: {
|
||||||
|
labelTKey: 'controlnet.lineartAnime',
|
||||||
|
descriptionTKey: 'controlnet.lineartAnimeDescription',
|
||||||
|
buildDefaults: () => ({
|
||||||
|
id: `lineart_anime_image_processor_${uuidv4()}`,
|
||||||
|
type: 'lineart_anime_image_processor',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
lineart_image_processor: {
|
||||||
|
labelTKey: 'controlnet.lineart',
|
||||||
|
descriptionTKey: 'controlnet.lineartDescription',
|
||||||
|
buildDefaults: () => ({
|
||||||
|
id: `lineart_image_processor_${uuidv4()}`,
|
||||||
|
type: 'lineart_image_processor',
|
||||||
|
coarse: false,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
mediapipe_face_processor: {
|
||||||
|
labelTKey: 'controlnet.mediapipeFace',
|
||||||
|
descriptionTKey: 'controlnet.mediapipeFaceDescription',
|
||||||
|
buildDefaults: () => ({
|
||||||
|
id: `mediapipe_face_processor_${uuidv4()}`,
|
||||||
|
type: 'mediapipe_face_processor',
|
||||||
|
max_faces: 1,
|
||||||
|
min_confidence: 0.5,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
midas_depth_image_processor: {
|
||||||
|
labelTKey: 'controlnet.depthMidas',
|
||||||
|
descriptionTKey: 'controlnet.depthMidasDescription',
|
||||||
|
buildDefaults: () => ({
|
||||||
|
id: `midas_depth_image_processor_${uuidv4()}`,
|
||||||
|
type: 'midas_depth_image_processor',
|
||||||
|
a_mult: 2,
|
||||||
|
bg_th: 0.1,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
mlsd_image_processor: {
|
||||||
|
labelTKey: 'controlnet.mlsd',
|
||||||
|
descriptionTKey: 'controlnet.mlsdDescription',
|
||||||
|
buildDefaults: () => ({
|
||||||
|
id: `mlsd_image_processor_${uuidv4()}`,
|
||||||
|
type: 'mlsd_image_processor',
|
||||||
|
thr_d: 0.1,
|
||||||
|
thr_v: 0.1,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
normalbae_image_processor: {
|
||||||
|
labelTKey: 'controlnet.normalBae',
|
||||||
|
descriptionTKey: 'controlnet.normalBaeDescription',
|
||||||
|
buildDefaults: () => ({
|
||||||
|
id: `normalbae_image_processor_${uuidv4()}`,
|
||||||
|
type: 'normalbae_image_processor',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
dw_openpose_image_processor: {
|
||||||
|
labelTKey: 'controlnet.dwOpenpose',
|
||||||
|
descriptionTKey: 'controlnet.dwOpenposeDescription',
|
||||||
|
buildDefaults: () => ({
|
||||||
|
id: `dw_openpose_image_processor_${uuidv4()}`,
|
||||||
|
type: 'dw_openpose_image_processor',
|
||||||
|
draw_body: true,
|
||||||
|
draw_face: false,
|
||||||
|
draw_hands: false,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
pidi_image_processor: {
|
||||||
|
labelTKey: 'controlnet.pidi',
|
||||||
|
descriptionTKey: 'controlnet.pidiDescription',
|
||||||
|
buildDefaults: () => ({
|
||||||
|
id: `pidi_image_processor_${uuidv4()}`,
|
||||||
|
type: 'pidi_image_processor',
|
||||||
|
scribble: false,
|
||||||
|
safe: false,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
zoe_depth_image_processor: {
|
||||||
|
labelTKey: 'controlnet.depthZoe',
|
||||||
|
descriptionTKey: 'controlnet.depthZoeDescription',
|
||||||
|
buildDefaults: () => ({
|
||||||
|
id: `zoe_depth_image_processor_${uuidv4()}`,
|
||||||
|
type: 'zoe_depth_image_processor',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
export const zProcessorType = z.enum([
|
||||||
|
'canny_image_processor',
|
||||||
|
'color_map_image_processor',
|
||||||
|
'content_shuffle_image_processor',
|
||||||
|
'depth_anything_image_processor',
|
||||||
|
'hed_image_processor',
|
||||||
|
'lineart_anime_image_processor',
|
||||||
|
'lineart_image_processor',
|
||||||
|
'mediapipe_face_processor',
|
||||||
|
'midas_depth_image_processor',
|
||||||
|
'mlsd_image_processor',
|
||||||
|
'normalbae_image_processor',
|
||||||
|
'dw_openpose_image_processor',
|
||||||
|
'pidi_image_processor',
|
||||||
|
'zoe_depth_image_processor',
|
||||||
|
]);
|
||||||
|
export type ProcessorType = z.infer<typeof zProcessorType>;
|
||||||
|
export const isControlAdapterProcessorType = (v: unknown): v is ProcessorType => zProcessorType.safeParse(v).success;
|
||||||
|
|
||||||
|
export const initialControlNet: Omit<ControlNetConfig, 'id'> = {
|
||||||
|
type: 'controlnet',
|
||||||
|
isEnabled: true,
|
||||||
|
model: null,
|
||||||
|
weight: 1,
|
||||||
|
beginEndStepPct: [0, 0],
|
||||||
|
controlMode: 'balanced',
|
||||||
|
image: null,
|
||||||
|
processedImage: null,
|
||||||
|
processorConfig: CONTROLNET_PROCESSORS.canny_image_processor.buildDefaults(),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const initialT2IAdapter: Omit<T2IAdapterConfig, 'id'> = {
|
||||||
|
type: 't2i_adapter',
|
||||||
|
isEnabled: true,
|
||||||
|
model: null,
|
||||||
|
weight: 1,
|
||||||
|
beginEndStepPct: [0, 0],
|
||||||
|
image: null,
|
||||||
|
processedImage: null,
|
||||||
|
processorConfig: CONTROLNET_PROCESSORS.canny_image_processor.buildDefaults(),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const initialIPAdapter: Omit<IPAdapterConfig, 'id'> = {
|
||||||
|
type: 'ip_adapter',
|
||||||
|
isEnabled: true,
|
||||||
|
image: null,
|
||||||
|
model: null,
|
||||||
|
beginEndStepPct: [0, 0],
|
||||||
|
method: 'full',
|
||||||
|
clipVisionModel: 'ViT-H',
|
||||||
|
weight: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const buildControlNet = (id: string, overrides?: Partial<ControlNetConfig>): ControlNetConfig => {
|
||||||
|
return merge(deepClone(initialControlNet), { id, overrides });
|
||||||
|
};
|
||||||
|
|
||||||
|
export const buildT2IAdapter = (id: string, overrides?: Partial<T2IAdapterConfig>): T2IAdapterConfig => {
|
||||||
|
return merge(deepClone(initialT2IAdapter), { id, overrides });
|
||||||
|
};
|
||||||
|
|
||||||
|
export const buildIPAdapter = (id: string, overrides?: Partial<IPAdapterConfig>): IPAdapterConfig => {
|
||||||
|
return merge(deepClone(initialIPAdapter), { id, overrides });
|
||||||
|
};
|
||||||
|
|
||||||
|
export const buildControlAdapterProcessor = (
|
||||||
|
modelConfig: ControlNetModelConfig | T2IAdapterModelConfig
|
||||||
|
): ProcessorConfig | null => {
|
||||||
|
const defaultPreprocessor = modelConfig.default_settings?.preprocessor;
|
||||||
|
if (!isControlAdapterProcessorType(defaultPreprocessor)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const processorConfig = CONTROLNET_PROCESSORS[defaultPreprocessor].buildDefaults(modelConfig.base);
|
||||||
|
return processorConfig;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const imageDTOToImageWithDims = ({ image_name, width, height }: ImageDTO): ImageWithDims => ({
|
||||||
|
imageName: image_name,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
});
|
@ -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 { isRegionalGuidanceLayer, regional_guidance_layer_NAME } from 'features/controlLayers/store/controlLayersSlice';
|
import { isRegionalGuidanceLayer, RG_LAYER_NAME } from 'features/controlLayers/store/controlLayersSlice';
|
||||||
import { renderers } from 'features/controlLayers/util/renderers';
|
import { renderers } from 'features/controlLayers/util/renderers';
|
||||||
import Konva from 'konva';
|
import Konva from 'konva';
|
||||||
import { assert } from 'tsafe';
|
import { assert } from 'tsafe';
|
||||||
@ -24,7 +24,7 @@ export const getRegionalPromptLayerBlobs = async (
|
|||||||
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>(`.${regional_guidance_layer_NAME}`);
|
const konvaLayers = stage.find<Konva.Layer>(`.${RG_LAYER_NAME}`);
|
||||||
const blobs: Record<string, Blob> = {};
|
const blobs: Record<string, Blob> = {};
|
||||||
|
|
||||||
// First remove all layers
|
// First remove all layers
|
||||||
|
@ -5,20 +5,20 @@ import {
|
|||||||
$tool,
|
$tool,
|
||||||
BACKGROUND_LAYER_ID,
|
BACKGROUND_LAYER_ID,
|
||||||
BACKGROUND_RECT_ID,
|
BACKGROUND_RECT_ID,
|
||||||
CONTROLNET_LAYER_IMAGE_NAME,
|
CA_LAYER_IMAGE_NAME,
|
||||||
CONTROLNET_LAYER_NAME,
|
CA_LAYER_NAME,
|
||||||
getControlNetLayerImageId,
|
getCALayerImageId,
|
||||||
getLayerBboxId,
|
getLayerBboxId,
|
||||||
getRegionalGuidanceLayerObjectGroupId,
|
getRGLayerObjectGroupId,
|
||||||
isControlAdapterLayer,
|
isControlAdapterLayer,
|
||||||
isRegionalGuidanceLayer,
|
isRegionalGuidanceLayer,
|
||||||
isRenderableLayer,
|
isRenderableLayer,
|
||||||
LAYER_BBOX_NAME,
|
LAYER_BBOX_NAME,
|
||||||
NO_LAYERS_MESSAGE_LAYER_ID,
|
NO_LAYERS_MESSAGE_LAYER_ID,
|
||||||
regional_guidance_layer_LINE_NAME,
|
RG_LAYER_LINE_NAME,
|
||||||
regional_guidance_layer_NAME,
|
RG_LAYER_NAME,
|
||||||
regional_guidance_layer_OBJECT_GROUP_NAME,
|
RG_LAYER_OBJECT_GROUP_NAME,
|
||||||
regional_guidance_layer_RECT_NAME,
|
RG_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,
|
||||||
@ -53,10 +53,10 @@ const STAGE_BG_DATAURL =
|
|||||||
const mapId = (object: { id: string }) => object.id;
|
const mapId = (object: { id: string }) => object.id;
|
||||||
|
|
||||||
const selectRenderableLayers = (n: Konva.Node) =>
|
const selectRenderableLayers = (n: Konva.Node) =>
|
||||||
n.name() === regional_guidance_layer_NAME || n.name() === CONTROLNET_LAYER_NAME;
|
n.name() === RG_LAYER_NAME || n.name() === CA_LAYER_NAME;
|
||||||
|
|
||||||
const selectVectorMaskObjects = (node: Konva.Node) => {
|
const selectVectorMaskObjects = (node: Konva.Node) => {
|
||||||
return node.name() === regional_guidance_layer_LINE_NAME || node.name() === regional_guidance_layer_RECT_NAME;
|
return node.name() === RG_LAYER_LINE_NAME || node.name() === RG_LAYER_RECT_NAME;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -141,7 +141,7 @@ const renderToolPreview = (
|
|||||||
isMouseOver: boolean,
|
isMouseOver: boolean,
|
||||||
brushSize: number
|
brushSize: number
|
||||||
) => {
|
) => {
|
||||||
const layerCount = stage.find(`.${regional_guidance_layer_NAME}`).length;
|
const layerCount = stage.find(`.${RG_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
|
||||||
@ -233,7 +233,7 @@ const createRegionalGuidanceLayer = (
|
|||||||
// 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: regional_guidance_layer_NAME,
|
name: RG_LAYER_NAME,
|
||||||
draggable: true,
|
draggable: true,
|
||||||
dragDistance: 0,
|
dragDistance: 0,
|
||||||
});
|
});
|
||||||
@ -265,8 +265,8 @@ const createRegionalGuidanceLayer = (
|
|||||||
|
|
||||||
// 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: getRegionalGuidanceLayerObjectGroupId(reduxLayer.id, uuidv4()),
|
id: getRGLayerObjectGroupId(reduxLayer.id, uuidv4()),
|
||||||
name: regional_guidance_layer_OBJECT_GROUP_NAME,
|
name: RG_LAYER_OBJECT_GROUP_NAME,
|
||||||
listening: false,
|
listening: false,
|
||||||
});
|
});
|
||||||
konvaLayer.add(konvaObjectGroup);
|
konvaLayer.add(konvaObjectGroup);
|
||||||
@ -285,7 +285,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: regional_guidance_layer_LINE_NAME,
|
name: RG_LAYER_LINE_NAME,
|
||||||
strokeWidth: reduxObject.strokeWidth,
|
strokeWidth: reduxObject.strokeWidth,
|
||||||
tension: 0,
|
tension: 0,
|
||||||
lineCap: 'round',
|
lineCap: 'round',
|
||||||
@ -307,7 +307,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: regional_guidance_layer_RECT_NAME,
|
name: RG_LAYER_RECT_NAME,
|
||||||
x: reduxObject.x,
|
x: reduxObject.x,
|
||||||
y: reduxObject.y,
|
y: reduxObject.y,
|
||||||
width: reduxObject.width,
|
width: reduxObject.width,
|
||||||
@ -347,7 +347,7 @@ const renderRegionalGuidanceLayer = (
|
|||||||
// 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>(`.${regional_guidance_layer_OBJECT_GROUP_NAME}`);
|
const konvaObjectGroup = konvaLayer.findOne<Konva.Group>(`.${RG_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.
|
||||||
@ -411,7 +411,7 @@ const renderRegionalGuidanceLayer = (
|
|||||||
const createControlNetLayer = (stage: Konva.Stage, reduxLayer: ControlAdapterLayer): Konva.Layer => {
|
const createControlNetLayer = (stage: Konva.Stage, reduxLayer: ControlAdapterLayer): Konva.Layer => {
|
||||||
const konvaLayer = new Konva.Layer({
|
const konvaLayer = new Konva.Layer({
|
||||||
id: reduxLayer.id,
|
id: reduxLayer.id,
|
||||||
name: CONTROLNET_LAYER_NAME,
|
name: CA_LAYER_NAME,
|
||||||
imageSmoothingEnabled: true,
|
imageSmoothingEnabled: true,
|
||||||
});
|
});
|
||||||
stage.add(konvaLayer);
|
stage.add(konvaLayer);
|
||||||
@ -420,7 +420,7 @@ const createControlNetLayer = (stage: Konva.Stage, reduxLayer: ControlAdapterLay
|
|||||||
|
|
||||||
const createControlNetLayerImage = (konvaLayer: Konva.Layer, image: HTMLImageElement): Konva.Image => {
|
const createControlNetLayerImage = (konvaLayer: Konva.Layer, image: HTMLImageElement): Konva.Image => {
|
||||||
const konvaImage = new Konva.Image({
|
const konvaImage = new Konva.Image({
|
||||||
name: CONTROLNET_LAYER_IMAGE_NAME,
|
name: CA_LAYER_IMAGE_NAME,
|
||||||
image,
|
image,
|
||||||
});
|
});
|
||||||
konvaLayer.add(konvaImage);
|
konvaLayer.add(konvaImage);
|
||||||
@ -438,11 +438,11 @@ const updateControlNetLayerImageSource = async (
|
|||||||
const imageDTO = await req.unwrap();
|
const imageDTO = await req.unwrap();
|
||||||
req.unsubscribe();
|
req.unsubscribe();
|
||||||
const image = new Image();
|
const image = new Image();
|
||||||
const imageId = getControlNetLayerImageId(reduxLayer.id, imageName);
|
const imageId = getCALayerImageId(reduxLayer.id, imageName);
|
||||||
image.onload = () => {
|
image.onload = () => {
|
||||||
// Find the existing image or create a new one - must find using the name, bc the id may have just changed
|
// Find the existing image or create a new one - must find using the name, bc the id may have just changed
|
||||||
const konvaImage =
|
const konvaImage =
|
||||||
konvaLayer.findOne<Konva.Image>(`.${CONTROLNET_LAYER_IMAGE_NAME}`) ??
|
konvaLayer.findOne<Konva.Image>(`.${CA_LAYER_IMAGE_NAME}`) ??
|
||||||
createControlNetLayerImage(konvaLayer, image);
|
createControlNetLayerImage(konvaLayer, image);
|
||||||
|
|
||||||
// Update the image's attributes
|
// Update the image's attributes
|
||||||
@ -457,7 +457,7 @@ const updateControlNetLayerImageSource = async (
|
|||||||
};
|
};
|
||||||
image.src = imageDTO.image_url;
|
image.src = imageDTO.image_url;
|
||||||
} else {
|
} else {
|
||||||
konvaLayer.findOne(`.${CONTROLNET_LAYER_IMAGE_NAME}`)?.destroy();
|
konvaLayer.findOne(`.${CA_LAYER_IMAGE_NAME}`)?.destroy();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -497,13 +497,13 @@ const updateControlNetLayerImageAttrs = (
|
|||||||
|
|
||||||
const renderControlNetLayer = (stage: Konva.Stage, reduxLayer: ControlAdapterLayer) => {
|
const renderControlNetLayer = (stage: Konva.Stage, reduxLayer: ControlAdapterLayer) => {
|
||||||
const konvaLayer = stage.findOne<Konva.Layer>(`#${reduxLayer.id}`) ?? createControlNetLayer(stage, reduxLayer);
|
const konvaLayer = stage.findOne<Konva.Layer>(`#${reduxLayer.id}`) ?? createControlNetLayer(stage, reduxLayer);
|
||||||
const konvaImage = konvaLayer.findOne<Konva.Image>(`.${CONTROLNET_LAYER_IMAGE_NAME}`);
|
const konvaImage = konvaLayer.findOne<Konva.Image>(`.${CA_LAYER_IMAGE_NAME}`);
|
||||||
const canvasImageSource = konvaImage?.image();
|
const canvasImageSource = konvaImage?.image();
|
||||||
let imageSourceNeedsUpdate = false;
|
let imageSourceNeedsUpdate = false;
|
||||||
if (canvasImageSource instanceof HTMLImageElement) {
|
if (canvasImageSource instanceof HTMLImageElement) {
|
||||||
if (
|
if (
|
||||||
reduxLayer.imageName &&
|
reduxLayer.imageName &&
|
||||||
canvasImageSource.id !== getControlNetLayerImageId(reduxLayer.id, reduxLayer.imageName)
|
canvasImageSource.id !== getCALayerImageId(reduxLayer.id, reduxLayer.imageName)
|
||||||
) {
|
) {
|
||||||
imageSourceNeedsUpdate = true;
|
imageSourceNeedsUpdate = true;
|
||||||
} else if (!reduxLayer.imageName) {
|
} else if (!reduxLayer.imageName) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user