mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
feat(ui): iterate on filter UI, flow
This commit is contained in:
parent
677dddcfc9
commit
7e400d876f
@ -1694,7 +1694,6 @@
|
|||||||
"objects_zero": "empty",
|
"objects_zero": "empty",
|
||||||
"objects_one": "{{count}} object",
|
"objects_one": "{{count}} object",
|
||||||
"objects_other": "{{count}} objects",
|
"objects_other": "{{count}} objects",
|
||||||
"filter": "Filter",
|
|
||||||
"convertToControlLayer": "Convert to Control Layer",
|
"convertToControlLayer": "Convert to Control Layer",
|
||||||
"convertToRasterLayer": "Convert to Raster Layer",
|
"convertToRasterLayer": "Convert to Raster Layer",
|
||||||
"enableTransparencyEffect": "Enable Transparency Effect",
|
"enableTransparencyEffect": "Enable Transparency Effect",
|
||||||
@ -1723,6 +1722,14 @@
|
|||||||
"solid": "Solid",
|
"solid": "Solid",
|
||||||
"checkerboard": "Checkerboard",
|
"checkerboard": "Checkerboard",
|
||||||
"dynamicGrid": "Dynamic Grid"
|
"dynamicGrid": "Dynamic Grid"
|
||||||
|
},
|
||||||
|
"filter": {
|
||||||
|
"filter": "Filter",
|
||||||
|
"filters": "Filters",
|
||||||
|
"filterType": "Filter Type",
|
||||||
|
"preview": "Preview",
|
||||||
|
"apply": "Apply",
|
||||||
|
"cancel": "Cancel"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"upscaling": {
|
"upscaling": {
|
||||||
|
@ -6,10 +6,10 @@ import { appStarted } from 'app/store/middleware/listenerMiddleware/listeners/ap
|
|||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import type { PartialAppConfig } from 'app/types/invokeai';
|
import type { PartialAppConfig } from 'app/types/invokeai';
|
||||||
import ImageUploadOverlay from 'common/components/ImageUploadOverlay';
|
import ImageUploadOverlay from 'common/components/ImageUploadOverlay';
|
||||||
|
import { useScopeFocusWatcher } from 'common/hooks/interactionScopes';
|
||||||
import { useClearStorage } from 'common/hooks/useClearStorage';
|
import { useClearStorage } from 'common/hooks/useClearStorage';
|
||||||
import { useFullscreenDropzone } from 'common/hooks/useFullscreenDropzone';
|
import { useFullscreenDropzone } from 'common/hooks/useFullscreenDropzone';
|
||||||
import { useGlobalHotkeys } from 'common/hooks/useGlobalHotkeys';
|
import { useGlobalHotkeys } from 'common/hooks/useGlobalHotkeys';
|
||||||
import { useScopeFocusWatcher } from 'common/hooks/interactionScopes';
|
|
||||||
import ChangeBoardModal from 'features/changeBoardModal/components/ChangeBoardModal';
|
import ChangeBoardModal from 'features/changeBoardModal/components/ChangeBoardModal';
|
||||||
import DeleteImageModal from 'features/deleteImageModal/components/DeleteImageModal';
|
import DeleteImageModal from 'features/deleteImageModal/components/DeleteImageModal';
|
||||||
import { DynamicPromptsModal } from 'features/dynamicPrompts/components/DynamicPromptsPreviewModal';
|
import { DynamicPromptsModal } from 'features/dynamicPrompts/components/DynamicPromptsPreviewModal';
|
||||||
|
@ -2,8 +2,13 @@ import { createAction } from '@reduxjs/toolkit';
|
|||||||
import { logger } from 'app/logging/logger';
|
import { logger } from 'app/logging/logger';
|
||||||
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
||||||
import { parseify } from 'common/util/serialize';
|
import { parseify } from 'common/util/serialize';
|
||||||
import { ipaImageChanged, rasterLayerAdded, rgIPAdapterImageChanged } from 'features/controlLayers/store/canvasV2Slice';
|
import {
|
||||||
import type { CanvasRasterLayerState } from 'features/controlLayers/store/types';
|
controlLayerAdded,
|
||||||
|
ipaImageChanged,
|
||||||
|
rasterLayerAdded,
|
||||||
|
rgIPAdapterImageChanged,
|
||||||
|
} from 'features/controlLayers/store/canvasV2Slice';
|
||||||
|
import type { CanvasControlLayerState, CanvasRasterLayerState } from 'features/controlLayers/store/types';
|
||||||
import { imageDTOToImageObject } from 'features/controlLayers/store/types';
|
import { imageDTOToImageObject } from 'features/controlLayers/store/types';
|
||||||
import type { TypesafeDraggableData, TypesafeDroppableData } from 'features/dnd/types';
|
import type { TypesafeDraggableData, TypesafeDroppableData } from 'features/dnd/types';
|
||||||
import { isValidDrop } from 'features/dnd/util/isValidDrop';
|
import { isValidDrop } from 'features/dnd/util/isValidDrop';
|
||||||
@ -99,7 +104,7 @@ export const addImageDroppedListener = (startAppListening: AppStartListening) =>
|
|||||||
* Image dropped on Raster layer
|
* Image dropped on Raster layer
|
||||||
*/
|
*/
|
||||||
if (
|
if (
|
||||||
overData.actionType === 'ADD_LAYER_FROM_IMAGE' &&
|
overData.actionType === 'ADD_RASTER_LAYER_FROM_IMAGE' &&
|
||||||
activeData.payloadType === 'IMAGE_DTO' &&
|
activeData.payloadType === 'IMAGE_DTO' &&
|
||||||
activeData.payload.imageDTO
|
activeData.payload.imageDTO
|
||||||
) {
|
) {
|
||||||
@ -113,6 +118,24 @@ export const addImageDroppedListener = (startAppListening: AppStartListening) =>
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Image dropped on Raster layer
|
||||||
|
*/
|
||||||
|
if (
|
||||||
|
overData.actionType === 'ADD_CONTROL_LAYER_FROM_IMAGE' &&
|
||||||
|
activeData.payloadType === 'IMAGE_DTO' &&
|
||||||
|
activeData.payload.imageDTO
|
||||||
|
) {
|
||||||
|
const imageObject = imageDTOToImageObject(activeData.payload.imageDTO);
|
||||||
|
const { x, y } = getState().canvasV2.bbox.rect;
|
||||||
|
const overrides: Partial<CanvasControlLayerState> = {
|
||||||
|
objects: [imageObject],
|
||||||
|
position: { x, y },
|
||||||
|
};
|
||||||
|
dispatch(controlLayerAdded({ overrides, isSelected: true }));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Image dropped on node image field
|
* Image dropped on node image field
|
||||||
*/
|
*/
|
||||||
|
@ -33,28 +33,23 @@ type IAINoImageFallbackProps = FlexProps & {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const IAINoContentFallback = memo((props: IAINoImageFallbackProps) => {
|
export const IAINoContentFallback = memo((props: IAINoImageFallbackProps) => {
|
||||||
const { icon = PiImageBold, boxSize = 16, sx, ...rest } = props;
|
const { icon = PiImageBold, boxSize = 16, ...rest } = props;
|
||||||
|
|
||||||
const styles = useMemo(
|
|
||||||
() => ({
|
|
||||||
w: 'full',
|
|
||||||
h: 'full',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
borderRadius: 'base',
|
|
||||||
flexDir: 'column',
|
|
||||||
gap: 2,
|
|
||||||
userSelect: 'none',
|
|
||||||
opacity: 0.7,
|
|
||||||
color: 'base.500',
|
|
||||||
fontSize: 'md',
|
|
||||||
...sx,
|
|
||||||
}),
|
|
||||||
[sx]
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex sx={styles} {...rest}>
|
<Flex
|
||||||
|
w="full"
|
||||||
|
h="full"
|
||||||
|
alignItems="center"
|
||||||
|
justifyContent="center"
|
||||||
|
borderRadius="base"
|
||||||
|
flexDir="column"
|
||||||
|
gap={2}
|
||||||
|
userSelect="none"
|
||||||
|
opacity={0.7}
|
||||||
|
color="base.500"
|
||||||
|
fontSize="md"
|
||||||
|
{...rest}
|
||||||
|
>
|
||||||
{icon && <Icon as={icon} boxSize={boxSize} opacity={0.7} />}
|
{icon && <Icon as={icon} boxSize={boxSize} opacity={0.7} />}
|
||||||
{props.label && <Text textAlign="center">{props.label}</Text>}
|
{props.label && <Text textAlign="center">{props.label}</Text>}
|
||||||
</Flex>
|
</Flex>
|
||||||
|
@ -1,12 +1,17 @@
|
|||||||
import { Flex } from '@invoke-ai/ui-library';
|
import { Flex } from '@invoke-ai/ui-library';
|
||||||
import IAIDroppable from 'common/components/IAIDroppable';
|
import IAIDroppable from 'common/components/IAIDroppable';
|
||||||
import type { AddLayerFromImageDropData } from 'features/dnd/types';
|
import type { AddControlLayerFromImageDropData, AddRasterLayerFromImageDropData } from 'features/dnd/types';
|
||||||
import { useIsImageViewerOpen } from 'features/gallery/components/ImageViewer/useImageViewer';
|
import { useIsImageViewerOpen } from 'features/gallery/components/ImageViewer/useImageViewer';
|
||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
|
|
||||||
const addLayerFromImageDropData: AddLayerFromImageDropData = {
|
const addRasterLayerFromImageDropData: AddRasterLayerFromImageDropData = {
|
||||||
id: 'add-layer-from-image-drop-data',
|
id: 'add-raster-layer-from-image-drop-data',
|
||||||
actionType: 'ADD_LAYER_FROM_IMAGE',
|
actionType: 'ADD_RASTER_LAYER_FROM_IMAGE',
|
||||||
|
};
|
||||||
|
|
||||||
|
const addControlLayerFromImageDropData: AddControlLayerFromImageDropData = {
|
||||||
|
id: 'add-control-layer-from-image-drop-data',
|
||||||
|
actionType: 'ADD_CONTROL_LAYER_FROM_IMAGE',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CanvasDropArea = memo(() => {
|
export const CanvasDropArea = memo(() => {
|
||||||
@ -17,9 +22,14 @@ export const CanvasDropArea = memo(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex position="absolute" top={0} right={0} bottom={0} left={0} gap={2} pointerEvents="none">
|
<>
|
||||||
<IAIDroppable dropLabel="Create Layer" data={addLayerFromImageDropData} />
|
<Flex position="absolute" top={0} right={0} bottom="50%" left={0} gap={2} pointerEvents="none">
|
||||||
</Flex>
|
<IAIDroppable dropLabel="Create Raster Layer" data={addRasterLayerFromImageDropData} />
|
||||||
|
</Flex>
|
||||||
|
<Flex position="absolute" top="50%" right={0} bottom={0} left={0} gap={2} pointerEvents="none">
|
||||||
|
<IAIDroppable dropLabel="Create Control Layer" data={addControlLayerFromImageDropData} />
|
||||||
|
</Flex>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
import { Combobox, FormControl, Tooltip } from '@invoke-ai/ui-library';
|
import { Combobox, FormControl, Tooltip } from '@invoke-ai/ui-library';
|
||||||
import { useAppSelector } from 'app/store/storeHooks';
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
import { useGroupedModelCombobox } from 'common/hooks/useGroupedModelCombobox';
|
import { useGroupedModelCombobox } from 'common/hooks/useGroupedModelCombobox';
|
||||||
|
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
|
||||||
|
import { $filterConfig, $filteringEntity } from 'features/controlLayers/store/canvasV2Slice';
|
||||||
|
import { IMAGE_FILTERS, isFilterType } from 'features/controlLayers/store/types';
|
||||||
import { memo, useCallback, useMemo } from 'react';
|
import { memo, useCallback, useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useControlNetAndT2IAdapterModels } from 'services/api/hooks/modelsByType';
|
import { useControlNetAndT2IAdapterModels } from 'services/api/hooks/modelsByType';
|
||||||
@ -13,6 +16,7 @@ type Props = {
|
|||||||
|
|
||||||
export const ControlLayerControlAdapterModel = memo(({ modelKey, onChange: onChangeModel }: Props) => {
|
export const ControlLayerControlAdapterModel = memo(({ modelKey, onChange: onChangeModel }: Props) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const entityIdentifier = useEntityIdentifierContext();
|
||||||
const currentBaseModel = useAppSelector((s) => s.canvasV2.params.model?.base);
|
const currentBaseModel = useAppSelector((s) => s.canvasV2.params.model?.base);
|
||||||
const [modelConfigs, { isLoading }] = useControlNetAndT2IAdapterModels();
|
const [modelConfigs, { isLoading }] = useControlNetAndT2IAdapterModels();
|
||||||
const selectedModel = useMemo(() => modelConfigs.find((m) => m.key === modelKey), [modelConfigs, modelKey]);
|
const selectedModel = useMemo(() => modelConfigs.find((m) => m.key === modelKey), [modelConfigs, modelKey]);
|
||||||
@ -23,8 +27,27 @@ export const ControlLayerControlAdapterModel = memo(({ modelKey, onChange: onCha
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
onChangeModel(modelConfig);
|
onChangeModel(modelConfig);
|
||||||
|
|
||||||
|
// When we set the model for the first time, we'll set the default filter settings and open the filter popup
|
||||||
|
|
||||||
|
if (modelKey) {
|
||||||
|
// If there is already a model key, this is not the first time we're setting the model
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the filter, preferring the model's default
|
||||||
|
if (isFilterType(modelConfig.default_settings?.preprocessor)) {
|
||||||
|
$filterConfig.set(IMAGE_FILTERS[modelConfig.default_settings.preprocessor].buildDefaults(modelConfig.base));
|
||||||
|
} else {
|
||||||
|
$filterConfig.set(IMAGE_FILTERS.canny_image_processor.buildDefaults(modelConfig.base));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open the filter popup by setting this entity as the filtering entity
|
||||||
|
if (!$filteringEntity.get()) {
|
||||||
|
$filteringEntity.set(entityIdentifier);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
[onChangeModel]
|
[entityIdentifier, modelKey, onChangeModel]
|
||||||
);
|
);
|
||||||
|
|
||||||
const getIsDisabled = useCallback(
|
const getIsDisabled = useCallback(
|
||||||
|
@ -3,6 +3,7 @@ import { Flex } from '@invoke-ai/ui-library';
|
|||||||
import { useScopeOnFocus } from 'common/hooks/interactionScopes';
|
import { useScopeOnFocus } from 'common/hooks/interactionScopes';
|
||||||
import { CanvasDropArea } from 'features/controlLayers/components/CanvasDropArea';
|
import { CanvasDropArea } from 'features/controlLayers/components/CanvasDropArea';
|
||||||
import { ControlLayersToolbar } from 'features/controlLayers/components/ControlLayersToolbar';
|
import { ControlLayersToolbar } from 'features/controlLayers/components/ControlLayersToolbar';
|
||||||
|
import { Filter } from 'features/controlLayers/components/Filters/Filter';
|
||||||
import { StageComponent } from 'features/controlLayers/components/StageComponent';
|
import { StageComponent } from 'features/controlLayers/components/StageComponent';
|
||||||
import { StagingAreaToolbar } from 'features/controlLayers/components/StagingArea/StagingAreaToolbar';
|
import { StagingAreaToolbar } from 'features/controlLayers/components/StagingArea/StagingAreaToolbar';
|
||||||
import { memo, useRef } from 'react';
|
import { memo, useRef } from 'react';
|
||||||
@ -31,6 +32,9 @@ export const CanvasEditor = memo(() => {
|
|||||||
<Flex position="absolute" bottom={2} gap={2} align="center" justify="center">
|
<Flex position="absolute" bottom={2} gap={2} align="center" justify="center">
|
||||||
<StagingAreaToolbar />
|
<StagingAreaToolbar />
|
||||||
</Flex>
|
</Flex>
|
||||||
|
<Flex position="absolute" bottom={16}>
|
||||||
|
<Filter />
|
||||||
|
</Flex>
|
||||||
<CanvasDropArea />
|
<CanvasDropArea />
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
|
@ -1,29 +0,0 @@
|
|||||||
/* eslint-disable i18next/no-literal-string */
|
|
||||||
import { useStore } from '@nanostores/react';
|
|
||||||
import { CanvasEntityList } from 'features/controlLayers/components/CanvasEntityList';
|
|
||||||
import { Filter } from 'features/controlLayers/components/Filters/Filter';
|
|
||||||
import { $filteringEntity } from 'features/controlLayers/store/canvasV2Slice';
|
|
||||||
import ResizeHandle from 'features/ui/components/tabs/ResizeHandle';
|
|
||||||
import { memo } from 'react';
|
|
||||||
import { Panel, PanelGroup } from 'react-resizable-panels';
|
|
||||||
|
|
||||||
export const ControlLayersPanelContent = memo(() => {
|
|
||||||
const filteringEntity = useStore($filteringEntity);
|
|
||||||
return (
|
|
||||||
<PanelGroup direction="vertical">
|
|
||||||
<Panel id="canvas-entity-list-panel" order={0}>
|
|
||||||
<CanvasEntityList />
|
|
||||||
</Panel>
|
|
||||||
{Boolean(filteringEntity) && (
|
|
||||||
<>
|
|
||||||
<ResizeHandle orientation="horizontal" />
|
|
||||||
<Panel id="filter-panel" order={1}>
|
|
||||||
<Filter />
|
|
||||||
</Panel>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</PanelGroup>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
ControlLayersPanelContent.displayName = 'ControlLayersPanelContent';
|
|
@ -1,15 +1,21 @@
|
|||||||
import { Button, ButtonGroup, Flex } from '@invoke-ai/ui-library';
|
import { Button, ButtonGroup, Flex, Heading } from '@invoke-ai/ui-library';
|
||||||
import { useStore } from '@nanostores/react';
|
import { useStore } from '@nanostores/react';
|
||||||
import { FilterSettings } from 'features/controlLayers/components/Filters/FilterSettings';
|
import { FilterSettings } from 'features/controlLayers/components/Filters/FilterSettings';
|
||||||
import { FilterTypeSelect } from 'features/controlLayers/components/Filters/FilterTypeSelect';
|
import { FilterTypeSelect } from 'features/controlLayers/components/Filters/FilterTypeSelect';
|
||||||
import { $canvasManager } from 'features/controlLayers/konva/CanvasManager';
|
import { $canvasManager } from 'features/controlLayers/konva/CanvasManager';
|
||||||
import { $filteringEntity } from 'features/controlLayers/store/canvasV2Slice';
|
import { $filterConfig, $filteringEntity, $isProcessingFilter } from 'features/controlLayers/store/canvasV2Slice';
|
||||||
|
import { type FilterConfig, IMAGE_FILTERS } from 'features/controlLayers/store/types';
|
||||||
import { memo, useCallback } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { PiCheckBold, PiShootingStarBold, PiXBold } from 'react-icons/pi';
|
||||||
|
|
||||||
export const Filter = memo(() => {
|
export const Filter = memo(() => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const filteringEntity = useStore($filteringEntity);
|
const filteringEntity = useStore($filteringEntity);
|
||||||
|
const filterConfig = useStore($filterConfig);
|
||||||
|
const isProcessingFilter = useStore($isProcessingFilter);
|
||||||
|
|
||||||
const preview = useCallback(() => {
|
const previewFilter = useCallback(() => {
|
||||||
if (!filteringEntity) {
|
if (!filteringEntity) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -24,7 +30,7 @@ export const Filter = memo(() => {
|
|||||||
entity.adapter.filter.previewFilter();
|
entity.adapter.filter.previewFilter();
|
||||||
}, [filteringEntity]);
|
}, [filteringEntity]);
|
||||||
|
|
||||||
const apply = useCallback(() => {
|
const applyFilter = useCallback(() => {
|
||||||
if (!filteringEntity) {
|
if (!filteringEntity) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -39,7 +45,7 @@ export const Filter = memo(() => {
|
|||||||
entity.adapter.filter.applyFilter();
|
entity.adapter.filter.applyFilter();
|
||||||
}, [filteringEntity]);
|
}, [filteringEntity]);
|
||||||
|
|
||||||
const cancel = useCallback(() => {
|
const cancelFilter = useCallback(() => {
|
||||||
if (!filteringEntity) {
|
if (!filteringEntity) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -54,21 +60,62 @@ export const Filter = memo(() => {
|
|||||||
entity.adapter.filter.cancelFilter();
|
entity.adapter.filter.cancelFilter();
|
||||||
}, [filteringEntity]);
|
}, [filteringEntity]);
|
||||||
|
|
||||||
|
const onChangeFilterConfig = useCallback((filterConfig: FilterConfig) => {
|
||||||
|
$filterConfig.set(filterConfig);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const onChangeFilterType = useCallback((filterType: FilterConfig['type']) => {
|
||||||
|
$filterConfig.set(IMAGE_FILTERS[filterType].buildDefaults());
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (!filteringEntity || !filterConfig) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex flexDir="column" gap={3} w="full" h="full">
|
<Flex
|
||||||
<FilterTypeSelect />
|
bg="base.800"
|
||||||
<ButtonGroup isAttached={false}>
|
borderRadius="base"
|
||||||
<Button onClick={preview} isDisabled={!filteringEntity}>
|
p={4}
|
||||||
Preview
|
flexDir="column"
|
||||||
|
gap={4}
|
||||||
|
w={420}
|
||||||
|
h="auto"
|
||||||
|
shadow="dark-lg"
|
||||||
|
transitionProperty="height"
|
||||||
|
transitionDuration="normal"
|
||||||
|
>
|
||||||
|
<Heading size="md" color="base.300" userSelect="none">
|
||||||
|
{t('controlLayers.filter.filter')}
|
||||||
|
</Heading>
|
||||||
|
<FilterTypeSelect filterType={filterConfig.type} onChange={onChangeFilterType} />
|
||||||
|
<FilterSettings filterConfig={filterConfig} onChange={onChangeFilterConfig} />
|
||||||
|
<ButtonGroup isAttached={false} size="sm" alignSelf="self-end">
|
||||||
|
<Button
|
||||||
|
leftIcon={<PiShootingStarBold />}
|
||||||
|
onClick={previewFilter}
|
||||||
|
isLoading={isProcessingFilter}
|
||||||
|
loadingText={t('controlLayers.filter.preview')}
|
||||||
|
>
|
||||||
|
{t('controlLayers.filter.preview')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={apply} isDisabled={!filteringEntity}>
|
<Button
|
||||||
Apply
|
leftIcon={<PiCheckBold />}
|
||||||
|
onClick={applyFilter}
|
||||||
|
isLoading={isProcessingFilter}
|
||||||
|
loadingText={t('controlLayers.filter.apply')}
|
||||||
|
>
|
||||||
|
{t('controlLayers.filter.apply')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={cancel} isDisabled={!filteringEntity}>
|
<Button
|
||||||
Cancel
|
leftIcon={<PiXBold />}
|
||||||
|
onClick={cancelFilter}
|
||||||
|
isLoading={isProcessingFilter}
|
||||||
|
loadingText={t('controlLayers.filter.cancel')}
|
||||||
|
>
|
||||||
|
{t('controlLayers.filter.cancel')}
|
||||||
</Button>
|
</Button>
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
<FilterSettings />
|
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
|
||||||
import { IAINoContentFallback } from 'common/components/IAIImageFallback';
|
import { IAINoContentFallback } from 'common/components/IAIImageFallback';
|
||||||
import { FilterCanny } from 'features/controlLayers/components/Filters/FilterCanny';
|
import { FilterCanny } from 'features/controlLayers/components/Filters/FilterCanny';
|
||||||
import { FilterColorMap } from 'features/controlLayers/components/Filters/FilterColorMap';
|
import { FilterColorMap } from 'features/controlLayers/components/Filters/FilterColorMap';
|
||||||
@ -11,67 +10,67 @@ import { FilterMediapipeFace } from 'features/controlLayers/components/Filters/F
|
|||||||
import { FilterMidasDepth } from 'features/controlLayers/components/Filters/FilterMidasDepth';
|
import { FilterMidasDepth } from 'features/controlLayers/components/Filters/FilterMidasDepth';
|
||||||
import { FilterMlsdImage } from 'features/controlLayers/components/Filters/FilterMlsdImage';
|
import { FilterMlsdImage } from 'features/controlLayers/components/Filters/FilterMlsdImage';
|
||||||
import { FilterPidi } from 'features/controlLayers/components/Filters/FilterPidi';
|
import { FilterPidi } from 'features/controlLayers/components/Filters/FilterPidi';
|
||||||
import { filterConfigChanged } from 'features/controlLayers/store/canvasV2Slice';
|
import type { FilterConfig } from 'features/controlLayers/store/types';
|
||||||
import { type FilterConfig, IMAGE_FILTERS } from 'features/controlLayers/store/types';
|
import { IMAGE_FILTERS } from 'features/controlLayers/store/types';
|
||||||
import { memo, useCallback } from 'react';
|
import { memo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
export const FilterSettings = memo(() => {
|
type Props = { filterConfig: FilterConfig; onChange: (filterConfig: FilterConfig) => void };
|
||||||
const dispatch = useAppDispatch();
|
|
||||||
|
export const FilterSettings = memo(({ filterConfig, onChange }: Props) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const config = useAppSelector((s) => s.canvasV2.filter.config);
|
|
||||||
const updateFilter = useCallback(
|
if (filterConfig.type === 'canny_image_processor') {
|
||||||
(config: FilterConfig) => {
|
return <FilterCanny config={filterConfig} onChange={onChange} />;
|
||||||
dispatch(filterConfigChanged({ config }));
|
}
|
||||||
},
|
|
||||||
[dispatch]
|
if (filterConfig.type === 'color_map_image_processor') {
|
||||||
|
return <FilterColorMap config={filterConfig} onChange={onChange} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filterConfig.type === 'content_shuffle_image_processor') {
|
||||||
|
return <FilterContentShuffle config={filterConfig} onChange={onChange} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filterConfig.type === 'depth_anything_image_processor') {
|
||||||
|
return <FilterDepthAnything config={filterConfig} onChange={onChange} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filterConfig.type === 'dw_openpose_image_processor') {
|
||||||
|
return <FilterDWOpenpose config={filterConfig} onChange={onChange} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filterConfig.type === 'hed_image_processor') {
|
||||||
|
return <FilterHed config={filterConfig} onChange={onChange} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filterConfig.type === 'lineart_image_processor') {
|
||||||
|
return <FilterLineart config={filterConfig} onChange={onChange} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filterConfig.type === 'mediapipe_face_processor') {
|
||||||
|
return <FilterMediapipeFace config={filterConfig} onChange={onChange} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filterConfig.type === 'midas_depth_image_processor') {
|
||||||
|
return <FilterMidasDepth config={filterConfig} onChange={onChange} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filterConfig.type === 'mlsd_image_processor') {
|
||||||
|
return <FilterMlsdImage config={filterConfig} onChange={onChange} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filterConfig.type === 'pidi_image_processor') {
|
||||||
|
return <FilterPidi config={filterConfig} onChange={onChange} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<IAINoContentFallback
|
||||||
|
py={4}
|
||||||
|
label={`${t(IMAGE_FILTERS[filterConfig.type].labelTKey)} has no settings`}
|
||||||
|
icon={null}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
if (config.type === 'canny_image_processor') {
|
|
||||||
return <FilterCanny config={config} onChange={updateFilter} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config.type === 'color_map_image_processor') {
|
|
||||||
return <FilterColorMap config={config} onChange={updateFilter} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config.type === 'content_shuffle_image_processor') {
|
|
||||||
return <FilterContentShuffle config={config} onChange={updateFilter} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config.type === 'depth_anything_image_processor') {
|
|
||||||
return <FilterDepthAnything config={config} onChange={updateFilter} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config.type === 'dw_openpose_image_processor') {
|
|
||||||
return <FilterDWOpenpose config={config} onChange={updateFilter} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config.type === 'hed_image_processor') {
|
|
||||||
return <FilterHed config={config} onChange={updateFilter} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config.type === 'lineart_image_processor') {
|
|
||||||
return <FilterLineart config={config} onChange={updateFilter} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config.type === 'mediapipe_face_processor') {
|
|
||||||
return <FilterMediapipeFace config={config} onChange={updateFilter} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config.type === 'midas_depth_image_processor') {
|
|
||||||
return <FilterMidasDepth config={config} onChange={updateFilter} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config.type === 'mlsd_image_processor') {
|
|
||||||
return <FilterMlsdImage config={config} onChange={updateFilter} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config.type === 'pidi_image_processor') {
|
|
||||||
return <FilterPidi config={config} onChange={updateFilter} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
return <IAINoContentFallback label={`${t(IMAGE_FILTERS[config.type].labelTKey)} has no settings`} icon={null} />;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
FilterSettings.displayName = 'Filter';
|
FilterSettings.displayName = 'Filter';
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import type { ComboboxOnChange } from '@invoke-ai/ui-library';
|
import type { ComboboxOnChange } from '@invoke-ai/ui-library';
|
||||||
import { Combobox, Flex, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
import { Combobox, Flex, FormControl, FormLabel } 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 { useAppSelector } from 'app/store/storeHooks';
|
||||||
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
|
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
|
||||||
import { filterSelected } from 'features/controlLayers/store/canvasV2Slice';
|
import type { FilterConfig } from 'features/controlLayers/store/types';
|
||||||
import { IMAGE_FILTERS, isFilterType } from 'features/controlLayers/store/types';
|
import { IMAGE_FILTERS, isFilterType } from 'features/controlLayers/store/types';
|
||||||
import { configSelector } from 'features/system/store/configSelectors';
|
import { configSelector } from 'features/system/store/configSelectors';
|
||||||
import { includes, map } from 'lodash-es';
|
import { includes, map } from 'lodash-es';
|
||||||
@ -16,10 +16,13 @@ const selectDisabledProcessors = createMemoizedSelector(
|
|||||||
(config) => config.sd.disabledControlNetProcessors
|
(config) => config.sd.disabledControlNetProcessors
|
||||||
);
|
);
|
||||||
|
|
||||||
export const FilterTypeSelect = memo(() => {
|
type Props = {
|
||||||
|
filterType: FilterConfig['type'];
|
||||||
|
onChange: (filterType: FilterConfig['type']) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const FilterTypeSelect = memo(({ filterType, onChange }: Props) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const dispatch = useAppDispatch();
|
|
||||||
const filterType = useAppSelector((s) => s.canvasV2.filter.config.type);
|
|
||||||
const disabledProcessors = useAppSelector(selectDisabledProcessors);
|
const disabledProcessors = useAppSelector(selectDisabledProcessors);
|
||||||
const options = useMemo(() => {
|
const options = useMemo(() => {
|
||||||
return map(IMAGE_FILTERS, ({ labelTKey }, type) => ({ value: type, label: t(labelTKey) })).filter(
|
return map(IMAGE_FILTERS, ({ labelTKey }, type) => ({ value: type, label: t(labelTKey) })).filter(
|
||||||
@ -33,9 +36,9 @@ export const FilterTypeSelect = memo(() => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
assert(isFilterType(v.value));
|
assert(isFilterType(v.value));
|
||||||
dispatch(filterSelected({ type: v.value }));
|
onChange(v.value);
|
||||||
},
|
},
|
||||||
[dispatch]
|
[onChange]
|
||||||
);
|
);
|
||||||
const value = useMemo(() => options.find((o) => o.value === filterType) ?? null, [options, filterType]);
|
const value = useMemo(() => options.find((o) => o.value === filterType) ?? null, [options, filterType]);
|
||||||
|
|
||||||
@ -43,7 +46,7 @@ export const FilterTypeSelect = memo(() => {
|
|||||||
<Flex gap={2}>
|
<Flex gap={2}>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<InformationalPopover feature="controlNetProcessor">
|
<InformationalPopover feature="controlNetProcessor">
|
||||||
<FormLabel m={0}>{t('controlLayers.filter')}</FormLabel>
|
<FormLabel m={0}>{t('controlLayers.filter.filterType')}</FormLabel>
|
||||||
</InformationalPopover>
|
</InformationalPopover>
|
||||||
<Combobox value={value} options={options} onChange={_onChange} isSearchable={false} isClearable={false} />
|
<Combobox value={value} options={options} onChange={_onChange} isSearchable={false} isClearable={false} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
@ -14,7 +14,7 @@ export const CanvasEntityMenuItemsFilter = memo(() => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<MenuItem onClick={filter} icon={<PiShootingStarBold />}>
|
<MenuItem onClick={filter} icon={<PiShootingStarBold />}>
|
||||||
{t('controlLayers.filter')}
|
{t('controlLayers.filter.filter')}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import type { JSONObject } from 'common/types';
|
import type { JSONObject, SerializableObject } from 'common/types';
|
||||||
import { parseify } from 'common/util/serialize';
|
|
||||||
import type { CanvasLayerAdapter } from 'features/controlLayers/konva/CanvasLayerAdapter';
|
import type { CanvasLayerAdapter } from 'features/controlLayers/konva/CanvasLayerAdapter';
|
||||||
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
|
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
|
||||||
import { getPrefixedId } from 'features/controlLayers/konva/util';
|
import { getPrefixedId } from 'features/controlLayers/konva/util';
|
||||||
@ -35,10 +34,10 @@ export class CanvasFilter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
previewFilter = async () => {
|
previewFilter = async () => {
|
||||||
const { config } = this.manager.stateApi.getFilterState();
|
const config = this.manager.stateApi.$filterConfig.get();
|
||||||
this.log.trace({ config }, 'Previewing filter');
|
this.log.trace({ config }, 'Previewing filter');
|
||||||
const dispatch = this.manager.stateApi._store.dispatch;
|
const dispatch = this.manager.stateApi._store.dispatch;
|
||||||
const rect = this.parent.transformer.getRelativeRect()
|
const rect = this.parent.transformer.getRelativeRect();
|
||||||
const imageDTO = await this.parent.renderer.rasterize(rect, false);
|
const imageDTO = await this.parent.renderer.rasterize(rect, false);
|
||||||
// TODO(psyche): I can't get TS to be happy, it thinkgs `config` is `never` but it should be inferred from the generic... I'll just cast it for now
|
// TODO(psyche): I can't get TS to be happy, it thinkgs `config` is `never` but it should be inferred from the generic... I'll just cast it for now
|
||||||
const filterNode = IMAGE_FILTERS[config.type].buildNode(imageDTO, config as never);
|
const filterNode = IMAGE_FILTERS[config.type].buildNode(imageDTO, config as never);
|
||||||
@ -66,22 +65,30 @@ export class CanvasFilter {
|
|||||||
if (event.origin !== this.id || event.invocation_source_id !== filterNode.id) {
|
if (event.origin !== this.id || event.invocation_source_id !== filterNode.id) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.log.trace({ event: parseify(event) }, 'Handling filter processing completion');
|
this.manager.socket.off('invocation_complete', listener);
|
||||||
|
|
||||||
|
this.log.trace({ event } as SerializableObject, 'Handling filter processing completion');
|
||||||
|
|
||||||
const { result } = event;
|
const { result } = event;
|
||||||
assert(result.type === 'image_output', `Processor did not return an image output, got: ${result}`);
|
assert(result.type === 'image_output', `Processor did not return an image output, got: ${result}`);
|
||||||
|
|
||||||
const imageDTO = await getImageDTO(result.image.image_name);
|
const imageDTO = await getImageDTO(result.image.image_name);
|
||||||
assert(imageDTO, "Failed to fetch processor output's image DTO");
|
assert(imageDTO, "Failed to fetch processor output's image DTO");
|
||||||
|
|
||||||
this.imageState = imageDTOToImageObject(imageDTO);
|
this.imageState = imageDTOToImageObject(imageDTO);
|
||||||
this.parent.renderer.clearBuffer();
|
this.parent.renderer.clearBuffer();
|
||||||
|
|
||||||
await this.parent.renderer.setBuffer(this.imageState);
|
await this.parent.renderer.setBuffer(this.imageState);
|
||||||
this.parent.renderer.hideObjects([this.imageState.id]);
|
|
||||||
this.manager.socket.off('invocation_complete', listener);
|
this.parent.renderer.hideObjects();
|
||||||
|
this.manager.stateApi.$isProcessingFilter.set(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
this.manager.socket.on('invocation_complete', listener);
|
this.manager.socket.on('invocation_complete', listener);
|
||||||
|
|
||||||
this.log.trace({ enqueueBatchArg: parseify(enqueueBatchArg) }, 'Enqueuing filter batch');
|
this.log.trace({ enqueueBatchArg } as SerializableObject, 'Enqueuing filter batch');
|
||||||
|
|
||||||
|
this.manager.stateApi.$isProcessingFilter.set(true);
|
||||||
dispatch(
|
dispatch(
|
||||||
queueApi.endpoints.enqueueBatch.initiate(enqueueBatchArg, {
|
queueApi.endpoints.enqueueBatch.initiate(enqueueBatchArg, {
|
||||||
fixedCacheKey: 'enqueueBatch',
|
fixedCacheKey: 'enqueueBatch',
|
||||||
@ -119,6 +126,7 @@ export class CanvasFilter {
|
|||||||
this.parent.renderer.showObjects();
|
this.parent.renderer.showObjects();
|
||||||
this.manager.stateApi.$filteringEntity.set(null);
|
this.manager.stateApi.$filteringEntity.set(null);
|
||||||
this.imageState = null;
|
this.imageState = null;
|
||||||
|
this.manager.stateApi.$isProcessingFilter.set(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
destroy = () => {
|
destroy = () => {
|
||||||
|
@ -4,9 +4,11 @@ import type { CanvasLayerAdapter } from 'features/controlLayers/konva/CanvasLaye
|
|||||||
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
|
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
|
||||||
import type { CanvasMaskAdapter } from 'features/controlLayers/konva/CanvasMaskAdapter';
|
import type { CanvasMaskAdapter } from 'features/controlLayers/konva/CanvasMaskAdapter';
|
||||||
import {
|
import {
|
||||||
|
$filterConfig,
|
||||||
$filteringEntity,
|
$filteringEntity,
|
||||||
$isDrawing,
|
$isDrawing,
|
||||||
$isMouseDown,
|
$isMouseDown,
|
||||||
|
$isProcessingFilter,
|
||||||
$lastAddedPoint,
|
$lastAddedPoint,
|
||||||
$lastCursorPos,
|
$lastCursorPos,
|
||||||
$lastMouseDownPos,
|
$lastMouseDownPos,
|
||||||
@ -161,9 +163,6 @@ export class CanvasStateApi {
|
|||||||
getIsSelected = (id: string) => {
|
getIsSelected = (id: string) => {
|
||||||
return this.getState().selectedEntityIdentifier?.id === id;
|
return this.getState().selectedEntityIdentifier?.id === id;
|
||||||
};
|
};
|
||||||
getLogLevel = () => {
|
|
||||||
return this._store.getState().system.consoleLogLevel;
|
|
||||||
};
|
|
||||||
getFilterState = () => {
|
getFilterState = () => {
|
||||||
return this._store.getState().canvasV2.filter;
|
return this._store.getState().canvasV2.filter;
|
||||||
};
|
};
|
||||||
@ -234,6 +233,8 @@ export class CanvasStateApi {
|
|||||||
|
|
||||||
$transformingEntity = $transformingEntity;
|
$transformingEntity = $transformingEntity;
|
||||||
$filteringEntity = $filteringEntity;
|
$filteringEntity = $filteringEntity;
|
||||||
|
$filterConfig = $filterConfig;
|
||||||
|
$isProcessingFilter = $isProcessingFilter;
|
||||||
|
|
||||||
$toolState: WritableAtom<CanvasV2State['tool']> = atom();
|
$toolState: WritableAtom<CanvasV2State['tool']> = atom();
|
||||||
$currentFill: WritableAtom<RgbaColor> = atom();
|
$currentFill: WritableAtom<RgbaColor> = atom();
|
||||||
|
@ -143,10 +143,6 @@ const initialState: CanvasV2State = {
|
|||||||
stagedImages: [],
|
stagedImages: [],
|
||||||
selectedStagedImageIndex: 0,
|
selectedStagedImageIndex: 0,
|
||||||
},
|
},
|
||||||
filter: {
|
|
||||||
autoProcess: true,
|
|
||||||
config: IMAGE_FILTERS.canny_image_processor.buildDefaults(),
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export function selectEntity(state: CanvasV2State, { id, type }: CanvasEntityIdentifier) {
|
export function selectEntity(state: CanvasV2State, { id, type }: CanvasEntityIdentifier) {
|
||||||
@ -486,12 +482,6 @@ export const canvasV2Slice = createSlice({
|
|||||||
state.inpaintMask = deepClone(initialState.inpaintMask);
|
state.inpaintMask = deepClone(initialState.inpaintMask);
|
||||||
state.selectedEntityIdentifier = deepClone(initialState.selectedEntityIdentifier);
|
state.selectedEntityIdentifier = deepClone(initialState.selectedEntityIdentifier);
|
||||||
},
|
},
|
||||||
filterSelected: (state, action: PayloadAction<{ type: FilterConfig['type'] }>) => {
|
|
||||||
state.filter.config = IMAGE_FILTERS[action.payload.type].buildDefaults();
|
|
||||||
},
|
|
||||||
filterConfigChanged: (state, action: PayloadAction<{ config: FilterConfig }>) => {
|
|
||||||
state.filter.config = action.payload.config;
|
|
||||||
},
|
|
||||||
rasterizationCachesInvalidated: (state) => {
|
rasterizationCachesInvalidated: (state) => {
|
||||||
// Invalidate the rasterization caches for all entities.
|
// Invalidate the rasterization caches for all entities.
|
||||||
|
|
||||||
@ -669,9 +659,6 @@ export const {
|
|||||||
sessionStagingAreaReset,
|
sessionStagingAreaReset,
|
||||||
sessionNextStagedImageSelected,
|
sessionNextStagedImageSelected,
|
||||||
sessionPrevStagedImageSelected,
|
sessionPrevStagedImageSelected,
|
||||||
// Filter
|
|
||||||
filterSelected,
|
|
||||||
filterConfigChanged,
|
|
||||||
} = canvasV2Slice.actions;
|
} = canvasV2Slice.actions;
|
||||||
|
|
||||||
export const selectCanvasV2Slice = (state: RootState) => state.canvasV2;
|
export const selectCanvasV2Slice = (state: RootState) => state.canvasV2;
|
||||||
@ -699,6 +686,8 @@ export const $lastCursorPos = atom<Coordinate | null>(null);
|
|||||||
export const $spaceKey = atom<boolean>(false);
|
export const $spaceKey = atom<boolean>(false);
|
||||||
export const $transformingEntity = atom<CanvasEntityIdentifier | null>(null);
|
export const $transformingEntity = atom<CanvasEntityIdentifier | null>(null);
|
||||||
export const $filteringEntity = atom<CanvasEntityIdentifier | null>(null);
|
export const $filteringEntity = atom<CanvasEntityIdentifier | null>(null);
|
||||||
|
export const $filterConfig = atom<FilterConfig>(IMAGE_FILTERS.canny_image_processor.buildDefaults());
|
||||||
|
export const $isProcessingFilter = atom(false);
|
||||||
|
|
||||||
export const canvasV2PersistConfig: PersistConfig<CanvasV2State> = {
|
export const canvasV2PersistConfig: PersistConfig<CanvasV2State> = {
|
||||||
name: canvasV2Slice.name,
|
name: canvasV2Slice.name,
|
||||||
|
@ -4,14 +4,8 @@ import { getPrefixedId } from 'features/controlLayers/konva/util';
|
|||||||
import { isEqual, merge } from 'lodash-es';
|
import { isEqual, merge } from 'lodash-es';
|
||||||
import { assert } from 'tsafe';
|
import { assert } from 'tsafe';
|
||||||
|
|
||||||
import type {
|
import type { CanvasControlLayerState, CanvasRasterLayerState, CanvasV2State, Rect } from './types';
|
||||||
CanvasControlLayerState,
|
import { initialControlNetV2 } from './types';
|
||||||
CanvasRasterLayerState,
|
|
||||||
CanvasV2State,
|
|
||||||
ControlNetConfig,
|
|
||||||
Rect,
|
|
||||||
T2IAdapterConfig,
|
|
||||||
} from './types';
|
|
||||||
|
|
||||||
export const selectRasterLayer = (state: CanvasV2State, id: string) =>
|
export const selectRasterLayer = (state: CanvasV2State, id: string) =>
|
||||||
state.rasterLayers.entities.find((layer) => layer.id === id);
|
state.rasterLayers.entities.find((layer) => layer.id === id);
|
||||||
@ -73,11 +67,8 @@ export const rasterLayersReducers = {
|
|||||||
state.rasterLayers.compositeRasterizationCache.push(action.payload);
|
state.rasterLayers.compositeRasterizationCache.push(action.payload);
|
||||||
},
|
},
|
||||||
rasterLayerConvertedToControlLayer: {
|
rasterLayerConvertedToControlLayer: {
|
||||||
reducer: (
|
reducer: (state, action: PayloadAction<{ id: string; newId: string }>) => {
|
||||||
state,
|
const { id, newId } = action.payload;
|
||||||
action: PayloadAction<{ id: string; newId: string; controlAdapter: ControlNetConfig | T2IAdapterConfig }>
|
|
||||||
) => {
|
|
||||||
const { id, newId, controlAdapter } = action.payload;
|
|
||||||
const layer = selectRasterLayer(state, id);
|
const layer = selectRasterLayer(state, id);
|
||||||
if (!layer) {
|
if (!layer) {
|
||||||
return;
|
return;
|
||||||
@ -88,7 +79,7 @@ export const rasterLayersReducers = {
|
|||||||
...deepClone(layer),
|
...deepClone(layer),
|
||||||
id: newId,
|
id: newId,
|
||||||
type: 'control_layer',
|
type: 'control_layer',
|
||||||
controlAdapter,
|
controlAdapter: deepClone(initialControlNetV2),
|
||||||
withTransparencyEffect: true,
|
withTransparencyEffect: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -103,7 +94,7 @@ export const rasterLayersReducers = {
|
|||||||
|
|
||||||
state.selectedEntityIdentifier = { type: controlLayerState.type, id: controlLayerState.id };
|
state.selectedEntityIdentifier = { type: controlLayerState.type, id: controlLayerState.id };
|
||||||
},
|
},
|
||||||
prepare: (payload: { id: string; controlAdapter: ControlNetConfig | T2IAdapterConfig }) => ({
|
prepare: (payload: { id: string }) => ({
|
||||||
payload: { ...payload, newId: getPrefixedId('control_layer') },
|
payload: { ...payload, newId: getPrefixedId('control_layer') },
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
@ -930,10 +930,6 @@ export type CanvasV2State = {
|
|||||||
stagedImages: StagingAreaImage[];
|
stagedImages: StagingAreaImage[];
|
||||||
selectedStagedImageIndex: number;
|
selectedStagedImageIndex: number;
|
||||||
};
|
};
|
||||||
filter: {
|
|
||||||
autoProcess: boolean;
|
|
||||||
config: FilterConfig;
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type StageAttrs = {
|
export type StageAttrs = {
|
||||||
|
@ -44,8 +44,12 @@ export type RGIPAdapterImageDropData = BaseDropData & {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export type AddLayerFromImageDropData = BaseDropData & {
|
export type AddRasterLayerFromImageDropData = BaseDropData & {
|
||||||
actionType: 'ADD_LAYER_FROM_IMAGE';
|
actionType: 'ADD_RASTER_LAYER_FROM_IMAGE';
|
||||||
|
};
|
||||||
|
|
||||||
|
export type AddControlLayerFromImageDropData = BaseDropData & {
|
||||||
|
actionType: 'ADD_CONTROL_LAYER_FROM_IMAGE';
|
||||||
};
|
};
|
||||||
|
|
||||||
type UpscaleInitialImageDropData = BaseDropData & {
|
type UpscaleInitialImageDropData = BaseDropData & {
|
||||||
@ -91,7 +95,8 @@ export type TypesafeDroppableData =
|
|||||||
| RGIPAdapterImageDropData
|
| RGIPAdapterImageDropData
|
||||||
| SelectForCompareDropData
|
| SelectForCompareDropData
|
||||||
| UpscaleInitialImageDropData
|
| UpscaleInitialImageDropData
|
||||||
| AddLayerFromImageDropData;
|
| AddRasterLayerFromImageDropData
|
||||||
|
| AddControlLayerFromImageDropData;
|
||||||
|
|
||||||
type BaseDragData = {
|
type BaseDragData = {
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -14,19 +14,13 @@ export const isValidDrop = (overData?: TypesafeDroppableData | null, activeData?
|
|||||||
|
|
||||||
switch (actionType) {
|
switch (actionType) {
|
||||||
case 'SET_CURRENT_IMAGE':
|
case 'SET_CURRENT_IMAGE':
|
||||||
return payloadType === 'IMAGE_DTO';
|
|
||||||
case 'SET_CA_IMAGE':
|
case 'SET_CA_IMAGE':
|
||||||
return payloadType === 'IMAGE_DTO';
|
|
||||||
case 'SET_IPA_IMAGE':
|
case 'SET_IPA_IMAGE':
|
||||||
return payloadType === 'IMAGE_DTO';
|
|
||||||
case 'SET_RG_IP_ADAPTER_IMAGE':
|
case 'SET_RG_IP_ADAPTER_IMAGE':
|
||||||
return payloadType === 'IMAGE_DTO';
|
case 'ADD_RASTER_LAYER_FROM_IMAGE':
|
||||||
case 'ADD_LAYER_FROM_IMAGE':
|
case 'ADD_CONTROL_LAYER_FROM_IMAGE':
|
||||||
return payloadType === 'IMAGE_DTO';
|
|
||||||
case 'SET_UPSCALE_INITIAL_IMAGE':
|
case 'SET_UPSCALE_INITIAL_IMAGE':
|
||||||
return payloadType === 'IMAGE_DTO';
|
|
||||||
case 'SET_NODES_IMAGE':
|
case 'SET_NODES_IMAGE':
|
||||||
return payloadType === 'IMAGE_DTO';
|
|
||||||
case 'SELECT_FOR_COMPARE':
|
case 'SELECT_FOR_COMPARE':
|
||||||
return payloadType === 'IMAGE_DTO';
|
return payloadType === 'IMAGE_DTO';
|
||||||
case 'ADD_TO_BOARD': {
|
case 'ADD_TO_BOARD': {
|
||||||
|
@ -4,7 +4,7 @@ import { useStore } from '@nanostores/react';
|
|||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { overlayScrollbarsParams } from 'common/components/OverlayScrollbars/constants';
|
import { overlayScrollbarsParams } from 'common/components/OverlayScrollbars/constants';
|
||||||
import { AddLayerButton } from 'features/controlLayers/components/AddLayerButton';
|
import { AddLayerButton } from 'features/controlLayers/components/AddLayerButton';
|
||||||
import { ControlLayersPanelContent } from 'features/controlLayers/components/ControlLayersPanelContent';
|
import { CanvasEntityList } from 'features/controlLayers/components/CanvasEntityList';
|
||||||
import { $isPreviewVisible } from 'features/controlLayers/store/canvasV2Slice';
|
import { $isPreviewVisible } from 'features/controlLayers/store/canvasV2Slice';
|
||||||
import { selectEntityCount } from 'features/controlLayers/store/selectors';
|
import { selectEntityCount } from 'features/controlLayers/store/selectors';
|
||||||
import { isImageViewerOpenChanged } from 'features/gallery/store/gallerySlice';
|
import { isImageViewerOpenChanged } from 'features/gallery/store/gallerySlice';
|
||||||
@ -115,7 +115,7 @@ const ParametersPanelTextToImage = () => {
|
|||||||
</Flex>
|
</Flex>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel p={0} w="full" h="full">
|
<TabPanel p={0} w="full" h="full">
|
||||||
<ControlLayersPanelContent />
|
<CanvasEntityList />
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
</TabPanels>
|
</TabPanels>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
Loading…
Reference in New Issue
Block a user