mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
feat(ui): implement cache for image rasterization, rip out some old controladapters code
This commit is contained in:
parent
abe8db8154
commit
e49b72ee4e
@ -9,7 +9,6 @@ import { addBatchEnqueuedListener } from 'app/store/middleware/listenerMiddlewar
|
||||
import { addDeleteBoardAndImagesFulfilledListener } from 'app/store/middleware/listenerMiddleware/listeners/boardAndImagesDeleted';
|
||||
import { addBoardIdSelectedListener } from 'app/store/middleware/listenerMiddleware/listeners/boardIdSelected';
|
||||
import { addBulkDownloadListeners } from 'app/store/middleware/listenerMiddleware/listeners/bulkDownload';
|
||||
import { addControlAdapterPreprocessor } from 'app/store/middleware/listenerMiddleware/listeners/controlAdapterPreprocessor';
|
||||
import { addEnqueueRequestedLinear } from 'app/store/middleware/listenerMiddleware/listeners/enqueueRequestedLinear';
|
||||
import { addEnqueueRequestedNodes } from 'app/store/middleware/listenerMiddleware/listeners/enqueueRequestedNodes';
|
||||
import { addGalleryImageClickedListener } from 'app/store/middleware/listenerMiddleware/listeners/galleryImageClicked';
|
||||
@ -133,4 +132,4 @@ addAdHocPostProcessingRequestedListener(startAppListening);
|
||||
addDynamicPromptsListener(startAppListening);
|
||||
|
||||
addSetDefaultSettingsListener(startAppListening);
|
||||
addControlAdapterPreprocessor(startAppListening);
|
||||
// addControlAdapterPreprocessor(startAppListening);
|
||||
|
@ -1,5 +1,5 @@
|
||||
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
||||
import { caAllDeleted, ipaAllDeleted, layerAllDeleted } from 'features/controlLayers/store/canvasV2Slice';
|
||||
import { ipaAllDeleted, layerAllDeleted } from 'features/controlLayers/store/canvasV2Slice';
|
||||
import { getImageUsage } from 'features/deleteImageModal/store/selectors';
|
||||
import { nodeEditorReset } from 'features/nodes/store/nodesSlice';
|
||||
import { imagesApi } from 'services/api/endpoints/images';
|
||||
@ -14,7 +14,7 @@ export const addDeleteBoardAndImagesFulfilledListener = (startAppListening: AppS
|
||||
|
||||
let wereLayersReset = false;
|
||||
let wasNodeEditorReset = false;
|
||||
let wereControlAdaptersReset = false;
|
||||
const wereControlAdaptersReset = false;
|
||||
let wereIPAdaptersReset = false;
|
||||
|
||||
const { nodes, canvasV2 } = getState();
|
||||
@ -31,10 +31,10 @@ export const addDeleteBoardAndImagesFulfilledListener = (startAppListening: AppS
|
||||
wasNodeEditorReset = true;
|
||||
}
|
||||
|
||||
if (imageUsage.isControlAdapterImage && !wereControlAdaptersReset) {
|
||||
dispatch(caAllDeleted());
|
||||
wereControlAdaptersReset = true;
|
||||
}
|
||||
// if (imageUsage.isControlAdapterImage && !wereControlAdaptersReset) {
|
||||
// dispatch(caAllDeleted());
|
||||
// wereControlAdaptersReset = true;
|
||||
// }
|
||||
|
||||
if (imageUsage.isIPAdapterImage && !wereIPAdaptersReset) {
|
||||
dispatch(ipaAllDeleted());
|
||||
|
@ -1,12 +1,7 @@
|
||||
import { logger } from 'app/logging/logger';
|
||||
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
||||
import type { AppDispatch, RootState } from 'app/store/store';
|
||||
import {
|
||||
caImageChanged,
|
||||
caProcessedImageChanged,
|
||||
entityDeleted,
|
||||
ipaImageChanged,
|
||||
} from 'features/controlLayers/store/canvasV2Slice';
|
||||
import { entityDeleted, ipaImageChanged } from 'features/controlLayers/store/canvasV2Slice';
|
||||
import { imageDeletionConfirmed } from 'features/deleteImageModal/store/actions';
|
||||
import { isModalOpenChanged } from 'features/deleteImageModal/store/slice';
|
||||
import { selectListImagesQueryArgs } from 'features/gallery/store/gallerySelectors';
|
||||
@ -39,14 +34,17 @@ const deleteNodesImages = (state: RootState, dispatch: AppDispatch, imageDTO: Im
|
||||
});
|
||||
};
|
||||
|
||||
const deleteControlAdapterImages = (state: RootState, dispatch: AppDispatch, imageDTO: ImageDTO) => {
|
||||
state.canvasV2.controlAdapters.entities.forEach(({ id, imageObject, processedImageObject }) => {
|
||||
if (imageObject?.image.image_name === imageDTO.image_name || processedImageObject?.image.image_name === imageDTO.image_name) {
|
||||
dispatch(caImageChanged({ id, imageDTO: null }));
|
||||
dispatch(caProcessedImageChanged({ id, imageDTO: null }));
|
||||
}
|
||||
});
|
||||
};
|
||||
// const deleteControlAdapterImages = (state: RootState, dispatch: AppDispatch, imageDTO: ImageDTO) => {
|
||||
// state.canvasV2.controlAdapters.entities.forEach(({ id, imageObject, processedImageObject }) => {
|
||||
// if (
|
||||
// imageObject?.image.image_name === imageDTO.image_name ||
|
||||
// processedImageObject?.image.image_name === imageDTO.image_name
|
||||
// ) {
|
||||
// dispatch(caImageChanged({ id, imageDTO: null }));
|
||||
// dispatch(caProcessedImageChanged({ id, imageDTO: null }));
|
||||
// }
|
||||
// });
|
||||
// };
|
||||
|
||||
const deleteIPAdapterImages = (state: RootState, dispatch: AppDispatch, imageDTO: ImageDTO) => {
|
||||
state.canvasV2.ipAdapters.entities.forEach(({ id, imageObject }) => {
|
||||
@ -120,7 +118,7 @@ export const addImageDeletionListeners = (startAppListening: AppStartListening)
|
||||
}
|
||||
|
||||
deleteNodesImages(state, dispatch, imageDTO);
|
||||
deleteControlAdapterImages(state, dispatch, imageDTO);
|
||||
// deleteControlAdapterImages(state, dispatch, imageDTO);
|
||||
deleteIPAdapterImages(state, dispatch, imageDTO);
|
||||
deleteLayerImages(state, dispatch, imageDTO);
|
||||
} catch {
|
||||
@ -161,7 +159,7 @@ export const addImageDeletionListeners = (startAppListening: AppStartListening)
|
||||
|
||||
imageDTOs.forEach((imageDTO) => {
|
||||
deleteNodesImages(state, dispatch, imageDTO);
|
||||
deleteControlAdapterImages(state, dispatch, imageDTO);
|
||||
// deleteControlAdapterImages(state, dispatch, imageDTO);
|
||||
deleteIPAdapterImages(state, dispatch, imageDTO);
|
||||
deleteLayerImages(state, dispatch, imageDTO);
|
||||
});
|
||||
|
@ -3,7 +3,6 @@ import { logger } from 'app/logging/logger';
|
||||
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
||||
import { parseify } from 'common/util/serialize';
|
||||
import {
|
||||
caImageChanged,
|
||||
ipaImageChanged,
|
||||
layerAdded,
|
||||
rgIPAdapterImageChanged,
|
||||
@ -60,18 +59,18 @@ export const addImageDroppedListener = (startAppListening: AppStartListening) =>
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Image dropped on Control Adapter Layer
|
||||
*/
|
||||
if (
|
||||
overData.actionType === 'SET_CA_IMAGE' &&
|
||||
activeData.payloadType === 'IMAGE_DTO' &&
|
||||
activeData.payload.imageDTO
|
||||
) {
|
||||
const { id } = overData.context;
|
||||
dispatch(caImageChanged({ id, imageDTO: activeData.payload.imageDTO }));
|
||||
return;
|
||||
}
|
||||
// /**
|
||||
// * Image dropped on Control Adapter Layer
|
||||
// */
|
||||
// if (
|
||||
// overData.actionType === 'SET_CA_IMAGE' &&
|
||||
// activeData.payloadType === 'IMAGE_DTO' &&
|
||||
// activeData.payload.imageDTO
|
||||
// ) {
|
||||
// const { id } = overData.context;
|
||||
// dispatch(caImageChanged({ id, imageDTO: activeData.payload.imageDTO }));
|
||||
// return;
|
||||
// }
|
||||
|
||||
/**
|
||||
* Image dropped on IP Adapter Layer
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { logger } from 'app/logging/logger';
|
||||
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
||||
import { caImageChanged, ipaImageChanged, rgIPAdapterImageChanged } from 'features/controlLayers/store/canvasV2Slice';
|
||||
import { ipaImageChanged, rgIPAdapterImageChanged } from 'features/controlLayers/store/canvasV2Slice';
|
||||
import { selectListBoardsQueryArgs } from 'features/gallery/store/gallerySelectors';
|
||||
import { fieldImageValueChanged } from 'features/nodes/store/nodesSlice';
|
||||
import { upscaleInitialImageChanged } from 'features/parameters/store/upscaleSlice';
|
||||
@ -79,12 +79,12 @@ export const addImageUploadedFulfilledListener = (startAppListening: AppStartLis
|
||||
return;
|
||||
}
|
||||
|
||||
if (postUploadAction?.type === 'SET_CA_IMAGE') {
|
||||
const { id } = postUploadAction;
|
||||
dispatch(caImageChanged({ id, imageDTO }));
|
||||
toast({ ...DEFAULT_UPLOADED_TOAST, description: t('toast.setControlImage') });
|
||||
return;
|
||||
}
|
||||
// if (postUploadAction?.type === 'SET_CA_IMAGE') {
|
||||
// const { id } = postUploadAction;
|
||||
// dispatch(caImageChanged({ id, imageDTO }));
|
||||
// toast({ ...DEFAULT_UPLOADED_TOAST, description: t('toast.setControlImage') });
|
||||
// return;
|
||||
// }
|
||||
|
||||
if (postUploadAction?.type === 'SET_IPA_IMAGE') {
|
||||
const { id } = postUploadAction;
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { logger } from 'app/logging/logger';
|
||||
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
||||
import {
|
||||
entityIsEnabledToggled,
|
||||
loraDeleted,
|
||||
modelChanged,
|
||||
vaeSelected,
|
||||
@ -50,14 +49,14 @@ export const addModelSelectedListener = (startAppListening: AppStartListening) =
|
||||
}
|
||||
|
||||
// handle incompatible controlnets
|
||||
state.canvasV2.controlAdapters.entities.forEach((ca) => {
|
||||
if (ca.model?.base !== newBaseModel) {
|
||||
modelsCleared += 1;
|
||||
if (ca.isEnabled) {
|
||||
dispatch(entityIsEnabledToggled({ entityIdentifier: { id: ca.id, type: 'control_adapter' } }));
|
||||
}
|
||||
}
|
||||
});
|
||||
// state.canvasV2.controlAdapters.entities.forEach((ca) => {
|
||||
// if (ca.model?.base !== newBaseModel) {
|
||||
// modelsCleared += 1;
|
||||
// if (ca.isEnabled) {
|
||||
// dispatch(entityIsEnabledToggled({ entityIdentifier: { id: ca.id, type: 'control_adapter' } }));
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
|
||||
if (modelsCleared > 0) {
|
||||
toast({
|
||||
|
@ -5,7 +5,6 @@ import type { JSONObject } from 'common/types';
|
||||
import {
|
||||
bboxHeightChanged,
|
||||
bboxWidthChanged,
|
||||
caModelChanged,
|
||||
ipaModelChanged,
|
||||
loraDeleted,
|
||||
modelChanged,
|
||||
@ -21,7 +20,6 @@ import type { Logger } from 'roarr';
|
||||
import { modelConfigsAdapterSelectors, modelsApi } from 'services/api/endpoints/models';
|
||||
import type { AnyModelConfig } from 'services/api/types';
|
||||
import {
|
||||
isControlNetOrT2IAdapterModelConfig,
|
||||
isIPAdapterModelConfig,
|
||||
isLoRAModelConfig,
|
||||
isNonRefinerMainModelConfig,
|
||||
@ -171,14 +169,14 @@ const handleLoRAModels: ModelHandler = (models, state, dispatch, _log) => {
|
||||
};
|
||||
|
||||
const handleControlAdapterModels: ModelHandler = (models, state, dispatch, _log) => {
|
||||
const caModels = models.filter(isControlNetOrT2IAdapterModelConfig);
|
||||
state.canvasV2.controlAdapters.entities.forEach((ca) => {
|
||||
const isModelAvailable = caModels.some((m) => m.key === ca.model?.key);
|
||||
if (isModelAvailable) {
|
||||
return;
|
||||
}
|
||||
dispatch(caModelChanged({ id: ca.id, modelConfig: null }));
|
||||
});
|
||||
// const caModels = models.filter(isControlNetOrT2IAdapterModelConfig);
|
||||
// state.canvasV2.controlAdapters.entities.forEach((ca) => {
|
||||
// const isModelAvailable = caModels.some((m) => m.key === ca.model?.key);
|
||||
// if (isModelAvailable) {
|
||||
// return;
|
||||
// }
|
||||
// dispatch(caModelChanged({ id: ca.id, modelConfig: null }));
|
||||
// });
|
||||
};
|
||||
|
||||
const handleIPAdapterModels: ModelHandler = (models, state, dispatch, _log) => {
|
||||
|
@ -125,41 +125,41 @@ const createSelector = (templates: Templates) =>
|
||||
reasons.push({ content: i18n.t('parameters.invoke.noModelSelected') });
|
||||
}
|
||||
|
||||
canvasV2.controlAdapters.entities
|
||||
.filter((ca) => ca.isEnabled)
|
||||
.forEach((ca, i) => {
|
||||
const layerLiteral = i18n.t('controlLayers.layers_one');
|
||||
const layerNumber = i + 1;
|
||||
const layerType = i18n.t(LAYER_TYPE_TO_TKEY[ca.type]);
|
||||
const prefix = `${layerLiteral} #${layerNumber} (${layerType})`;
|
||||
const problems: string[] = [];
|
||||
// Must have model
|
||||
if (!ca.model) {
|
||||
problems.push(i18n.t('parameters.invoke.layer.controlAdapterNoModelSelected'));
|
||||
}
|
||||
// Model base must match
|
||||
if (ca.model?.base !== model?.base) {
|
||||
problems.push(i18n.t('parameters.invoke.layer.controlAdapterIncompatibleBaseModel'));
|
||||
}
|
||||
// Must have a control image OR, if it has a processor, it must have a processed image
|
||||
if (!ca.imageObject) {
|
||||
problems.push(i18n.t('parameters.invoke.layer.controlAdapterNoImageSelected'));
|
||||
} else if (ca.processorConfig && !ca.processedImageObject) {
|
||||
problems.push(i18n.t('parameters.invoke.layer.controlAdapterImageNotProcessed'));
|
||||
}
|
||||
// T2I Adapters require images have dimensions that are multiples of 64 (SD1.5) or 32 (SDXL)
|
||||
if (ca.adapterType === 't2i_adapter') {
|
||||
const multiple = model?.base === 'sdxl' ? 32 : 64;
|
||||
if (bbox.rect.width % multiple !== 0 || bbox.rect.height % multiple !== 0) {
|
||||
problems.push(i18n.t('parameters.invoke.layer.t2iAdapterIncompatibleDimensions', { multiple }));
|
||||
}
|
||||
}
|
||||
// canvasV2.controlAdapters.entities
|
||||
// .filter((ca) => ca.isEnabled)
|
||||
// .forEach((ca, i) => {
|
||||
// const layerLiteral = i18n.t('controlLayers.layers_one');
|
||||
// const layerNumber = i + 1;
|
||||
// const layerType = i18n.t(LAYER_TYPE_TO_TKEY[ca.type]);
|
||||
// const prefix = `${layerLiteral} #${layerNumber} (${layerType})`;
|
||||
// const problems: string[] = [];
|
||||
// // Must have model
|
||||
// if (!ca.model) {
|
||||
// problems.push(i18n.t('parameters.invoke.layer.controlAdapterNoModelSelected'));
|
||||
// }
|
||||
// // Model base must match
|
||||
// if (ca.model?.base !== model?.base) {
|
||||
// problems.push(i18n.t('parameters.invoke.layer.controlAdapterIncompatibleBaseModel'));
|
||||
// }
|
||||
// // Must have a control image OR, if it has a processor, it must have a processed image
|
||||
// if (!ca.imageObject) {
|
||||
// problems.push(i18n.t('parameters.invoke.layer.controlAdapterNoImageSelected'));
|
||||
// } else if (ca.processorConfig && !ca.processedImageObject) {
|
||||
// problems.push(i18n.t('parameters.invoke.layer.controlAdapterImageNotProcessed'));
|
||||
// }
|
||||
// // T2I Adapters require images have dimensions that are multiples of 64 (SD1.5) or 32 (SDXL)
|
||||
// if (ca.adapterType === 't2i_adapter') {
|
||||
// const multiple = model?.base === 'sdxl' ? 32 : 64;
|
||||
// if (bbox.rect.width % multiple !== 0 || bbox.rect.height % multiple !== 0) {
|
||||
// problems.push(i18n.t('parameters.invoke.layer.t2iAdapterIncompatibleDimensions', { multiple }));
|
||||
// }
|
||||
// }
|
||||
|
||||
if (problems.length) {
|
||||
const content = upperFirst(problems.join(', '));
|
||||
reasons.push({ prefix, content });
|
||||
}
|
||||
});
|
||||
// if (problems.length) {
|
||||
// const content = upperFirst(problems.join(', '));
|
||||
// reasons.push({ prefix, content });
|
||||
// }
|
||||
// });
|
||||
|
||||
canvasV2.ipAdapters.entities
|
||||
.filter((ipa) => ipa.isEnabled)
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { Flex } from '@invoke-ai/ui-library';
|
||||
import ScrollableContent from 'common/components/OverlayScrollbars/ScrollableContent';
|
||||
import { ControlAdapterList } from 'features/controlLayers/components/ControlAdapter/ControlAdapterList';
|
||||
import { InpaintMask } from 'features/controlLayers/components/InpaintMask/InpaintMask';
|
||||
import { IPAdapterList } from 'features/controlLayers/components/IPAdapter/IPAdapterList';
|
||||
import { LayerEntityList } from 'features/controlLayers/components/Layer/LayerEntityList';
|
||||
@ -13,7 +12,6 @@ export const CanvasEntityList = memo(() => {
|
||||
<Flex flexDir="column" gap={2} data-testid="control-layers-layer-list">
|
||||
<InpaintMask />
|
||||
<RegionalGuidanceEntityList />
|
||||
<ControlAdapterList />
|
||||
<IPAdapterList />
|
||||
<LayerEntityList />
|
||||
</Flex>
|
||||
|
@ -1,39 +0,0 @@
|
||||
import { Spacer, useDisclosure } from '@invoke-ai/ui-library';
|
||||
import { CanvasEntityContainer } from 'features/controlLayers/components/common/CanvasEntityContainer';
|
||||
import { CanvasEntityDeleteButton } from 'features/controlLayers/components/common/CanvasEntityDeleteButton';
|
||||
import { CanvasEntityEnabledToggle } from 'features/controlLayers/components/common/CanvasEntityEnabledToggle';
|
||||
import { CanvasEntityHeader } from 'features/controlLayers/components/common/CanvasEntityHeader';
|
||||
import { CanvasEntityTitle } from 'features/controlLayers/components/common/CanvasEntityTitle';
|
||||
import { ControlAdapterActionsMenu } from 'features/controlLayers/components/ControlAdapter/ControlAdapterActionsMenu';
|
||||
import { ControlAdapterOpacityAndFilter } from 'features/controlLayers/components/ControlAdapter/ControlAdapterOpacityAndFilter';
|
||||
import { ControlAdapterSettings } from 'features/controlLayers/components/ControlAdapter/ControlAdapterSettings';
|
||||
import { EntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
|
||||
import type { CanvasEntityIdentifier } from 'features/controlLayers/store/types';
|
||||
import { memo, useMemo } from 'react';
|
||||
|
||||
type Props = {
|
||||
id: string;
|
||||
};
|
||||
|
||||
export const ControlAdapter = memo(({ id }: Props) => {
|
||||
const entityIdentifier = useMemo<CanvasEntityIdentifier>(() => ({ id, type: 'control_adapter' }), [id]);
|
||||
const { isOpen, onToggle } = useDisclosure({ defaultIsOpen: true });
|
||||
|
||||
return (
|
||||
<EntityIdentifierContext.Provider value={entityIdentifier}>
|
||||
<CanvasEntityContainer>
|
||||
<CanvasEntityHeader onDoubleClick={onToggle}>
|
||||
<CanvasEntityEnabledToggle />
|
||||
<CanvasEntityTitle />
|
||||
<Spacer />
|
||||
<ControlAdapterOpacityAndFilter />
|
||||
<ControlAdapterActionsMenu />
|
||||
<CanvasEntityDeleteButton />
|
||||
</CanvasEntityHeader>
|
||||
{isOpen && <ControlAdapterSettings />}
|
||||
</CanvasEntityContainer>
|
||||
</EntityIdentifierContext.Provider>
|
||||
);
|
||||
});
|
||||
|
||||
ControlAdapter.displayName = 'ControlAdapter';
|
@ -1,17 +0,0 @@
|
||||
import { Menu, MenuList } from '@invoke-ai/ui-library';
|
||||
import { CanvasEntityActionMenuItems } from 'features/controlLayers/components/common/CanvasEntityActionMenuItems';
|
||||
import { CanvasEntityMenuButton } from 'features/controlLayers/components/common/CanvasEntityMenuButton';
|
||||
import { memo } from 'react';
|
||||
|
||||
export const ControlAdapterActionsMenu = memo(() => {
|
||||
return (
|
||||
<Menu>
|
||||
<CanvasEntityMenuButton />
|
||||
<MenuList>
|
||||
<CanvasEntityActionMenuItems />
|
||||
</MenuList>
|
||||
</Menu>
|
||||
);
|
||||
});
|
||||
|
||||
ControlAdapterActionsMenu.displayName = 'ControlAdapterActionsMenu';
|
@ -1,229 +0,0 @@
|
||||
import { Box, Flex, Spinner, useShiftModifier } from '@invoke-ai/ui-library';
|
||||
import { skipToken } from '@reduxjs/toolkit/query';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import IAIDndImage from 'common/components/IAIDndImage';
|
||||
import IAIDndImageIcon from 'common/components/IAIDndImageIcon';
|
||||
import { bboxHeightChanged, bboxWidthChanged } from 'features/controlLayers/store/canvasV2Slice';
|
||||
import { selectOptimalDimension } from 'features/controlLayers/store/selectors';
|
||||
import type { CanvasControlAdapterState } from 'features/controlLayers/store/types';
|
||||
import type { ImageDraggableData, TypesafeDroppableData } from 'features/dnd/types';
|
||||
import { calculateNewSize } from 'features/parameters/components/DocumentSize/calculateNewSize';
|
||||
import { memo, useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PiArrowCounterClockwiseBold, PiFloppyDiskBold, PiRulerBold } from 'react-icons/pi';
|
||||
import {
|
||||
useAddImageToBoardMutation,
|
||||
useChangeImageIsIntermediateMutation,
|
||||
useGetImageDTOQuery,
|
||||
useRemoveImageFromBoardMutation,
|
||||
} from 'services/api/endpoints/images';
|
||||
import type { ImageDTO, PostUploadAction } from 'services/api/types';
|
||||
|
||||
type Props = {
|
||||
controlAdapter: CanvasControlAdapterState;
|
||||
onChangeImage: (imageDTO: ImageDTO | null) => void;
|
||||
droppableData: TypesafeDroppableData;
|
||||
postUploadAction: PostUploadAction;
|
||||
onErrorLoadingImage: () => void;
|
||||
onErrorLoadingProcessedImage: () => void;
|
||||
};
|
||||
|
||||
export const ControlAdapterImagePreview = memo(
|
||||
({
|
||||
controlAdapter,
|
||||
onChangeImage,
|
||||
droppableData,
|
||||
postUploadAction,
|
||||
onErrorLoadingImage,
|
||||
onErrorLoadingProcessedImage,
|
||||
}: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
const autoAddBoardId = useAppSelector((s) => s.gallery.autoAddBoardId);
|
||||
const isConnected = useAppSelector((s) => s.system.isConnected);
|
||||
const optimalDimension = useAppSelector(selectOptimalDimension);
|
||||
const shift = useShiftModifier();
|
||||
|
||||
const [isMouseOverImage, setIsMouseOverImage] = useState(false);
|
||||
|
||||
const { currentData: controlImage, isError: isErrorControlImage } = useGetImageDTOQuery(
|
||||
controlAdapter.imageObject?.image.image_name ?? skipToken
|
||||
);
|
||||
const { currentData: processedControlImage, isError: isErrorProcessedControlImage } = useGetImageDTOQuery(
|
||||
controlAdapter.processedImageObject?.image.image_name ?? skipToken
|
||||
);
|
||||
|
||||
const [changeIsIntermediate] = useChangeImageIsIntermediateMutation();
|
||||
const [addToBoard] = useAddImageToBoardMutation();
|
||||
const [removeFromBoard] = useRemoveImageFromBoardMutation();
|
||||
const handleResetControlImage = useCallback(() => {
|
||||
onChangeImage(null);
|
||||
}, [onChangeImage]);
|
||||
|
||||
const handleSaveControlImage = useCallback(async () => {
|
||||
if (!processedControlImage) {
|
||||
return;
|
||||
}
|
||||
|
||||
await changeIsIntermediate({
|
||||
imageDTO: processedControlImage,
|
||||
is_intermediate: false,
|
||||
}).unwrap();
|
||||
|
||||
if (autoAddBoardId !== 'none') {
|
||||
addToBoard({
|
||||
imageDTO: processedControlImage,
|
||||
board_id: autoAddBoardId,
|
||||
});
|
||||
} else {
|
||||
removeFromBoard({ imageDTO: processedControlImage });
|
||||
}
|
||||
}, [processedControlImage, changeIsIntermediate, autoAddBoardId, addToBoard, removeFromBoard]);
|
||||
|
||||
const handleSetControlImageToDimensions = useCallback(() => {
|
||||
if (!controlImage) {
|
||||
return;
|
||||
}
|
||||
|
||||
const options = { updateAspectRatio: true, clamp: true };
|
||||
|
||||
if (shift) {
|
||||
const { width, height } = controlImage;
|
||||
dispatch(bboxWidthChanged({ width, ...options }));
|
||||
dispatch(bboxHeightChanged({ height, ...options }));
|
||||
} else {
|
||||
const { width, height } = calculateNewSize(
|
||||
controlImage.width / controlImage.height,
|
||||
optimalDimension * optimalDimension
|
||||
);
|
||||
dispatch(bboxWidthChanged({ width, ...options }));
|
||||
dispatch(bboxHeightChanged({ height, ...options }));
|
||||
}
|
||||
}, [controlImage, dispatch, optimalDimension, shift]);
|
||||
|
||||
const handleMouseEnter = useCallback(() => {
|
||||
setIsMouseOverImage(true);
|
||||
}, []);
|
||||
|
||||
const handleMouseLeave = useCallback(() => {
|
||||
setIsMouseOverImage(false);
|
||||
}, []);
|
||||
|
||||
const draggableData = useMemo<ImageDraggableData | undefined>(() => {
|
||||
if (controlImage) {
|
||||
return {
|
||||
id: controlAdapter.id,
|
||||
payloadType: 'IMAGE_DTO',
|
||||
payload: { imageDTO: controlImage },
|
||||
};
|
||||
}
|
||||
}, [controlImage, controlAdapter.id]);
|
||||
|
||||
const shouldShowProcessedImage =
|
||||
controlImage &&
|
||||
processedControlImage &&
|
||||
!isMouseOverImage &&
|
||||
!controlAdapter.processorPendingBatchId &&
|
||||
controlAdapter.processorConfig !== null;
|
||||
|
||||
useEffect(() => {
|
||||
if (!isConnected) {
|
||||
return;
|
||||
}
|
||||
if (isErrorControlImage) {
|
||||
onErrorLoadingImage();
|
||||
}
|
||||
if (isErrorProcessedControlImage) {
|
||||
onErrorLoadingProcessedImage();
|
||||
}
|
||||
}, [
|
||||
handleResetControlImage,
|
||||
isConnected,
|
||||
isErrorControlImage,
|
||||
isErrorProcessedControlImage,
|
||||
onErrorLoadingImage,
|
||||
onErrorLoadingProcessedImage,
|
||||
]);
|
||||
|
||||
return (
|
||||
<Flex
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
position="relative"
|
||||
w={36}
|
||||
h={36}
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
>
|
||||
<IAIDndImage
|
||||
draggableData={draggableData}
|
||||
droppableData={droppableData}
|
||||
imageDTO={controlImage}
|
||||
isDropDisabled={shouldShowProcessedImage}
|
||||
postUploadAction={postUploadAction}
|
||||
/>
|
||||
|
||||
<Box
|
||||
position="absolute"
|
||||
top={0}
|
||||
insetInlineStart={0}
|
||||
w="full"
|
||||
h="full"
|
||||
opacity={shouldShowProcessedImage ? 1 : 0}
|
||||
transitionProperty="common"
|
||||
transitionDuration="normal"
|
||||
pointerEvents="none"
|
||||
>
|
||||
<IAIDndImage
|
||||
draggableData={draggableData}
|
||||
droppableData={droppableData}
|
||||
imageDTO={processedControlImage}
|
||||
isUploadDisabled={true}
|
||||
onError={handleResetControlImage}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{controlImage && (
|
||||
<Flex position="absolute" flexDir="column" top={1} insetInlineEnd={1} gap={1}>
|
||||
<IAIDndImageIcon
|
||||
onClick={handleResetControlImage}
|
||||
icon={<PiArrowCounterClockwiseBold size={16} />}
|
||||
tooltip={t('controlnet.resetControlImage')}
|
||||
/>
|
||||
<IAIDndImageIcon
|
||||
onClick={handleSaveControlImage}
|
||||
icon={<PiFloppyDiskBold size={16} />}
|
||||
tooltip={t('controlnet.saveControlImage')}
|
||||
/>
|
||||
<IAIDndImageIcon
|
||||
onClick={handleSetControlImageToDimensions}
|
||||
icon={<PiRulerBold size={16} />}
|
||||
tooltip={
|
||||
shift ? t('controlnet.setControlImageDimensionsForce') : t('controlnet.setControlImageDimensions')
|
||||
}
|
||||
/>
|
||||
</Flex>
|
||||
)}
|
||||
|
||||
{controlAdapter.processorPendingBatchId !== null && (
|
||||
<Flex
|
||||
position="absolute"
|
||||
top={0}
|
||||
insetInlineStart={0}
|
||||
w="full"
|
||||
h="full"
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
opacity={0.8}
|
||||
borderRadius="base"
|
||||
bg="base.900"
|
||||
>
|
||||
<Spinner size="xl" color="base.400" />
|
||||
</Flex>
|
||||
)}
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
ControlAdapterImagePreview.displayName = 'ControlAdapterImagePreview';
|
@ -1,38 +0,0 @@
|
||||
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { CanvasEntityGroupTitle } from 'features/controlLayers/components/common/CanvasEntityGroupTitle';
|
||||
import { ControlAdapter } from 'features/controlLayers/components/ControlAdapter/ControlAdapter';
|
||||
import { mapId } from 'features/controlLayers/konva/util';
|
||||
import { selectCanvasV2Slice } from 'features/controlLayers/store/canvasV2Slice';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const selectEntityIds = createMemoizedSelector(selectCanvasV2Slice, (canvasV2) => {
|
||||
return canvasV2.controlAdapters.entities.map(mapId).reverse();
|
||||
});
|
||||
|
||||
export const ControlAdapterList = memo(() => {
|
||||
const { t } = useTranslation();
|
||||
const isSelected = useAppSelector((s) => Boolean(s.canvasV2.selectedEntityIdentifier?.type === 'control_adapter'));
|
||||
const caIds = useAppSelector(selectEntityIds);
|
||||
|
||||
if (caIds.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (caIds.length > 0) {
|
||||
return (
|
||||
<>
|
||||
<CanvasEntityGroupTitle
|
||||
title={t('controlLayers.controlAdapters_withCount', { count: caIds.length })}
|
||||
isSelected={isSelected}
|
||||
/>
|
||||
{caIds.map((id) => (
|
||||
<ControlAdapter key={id} id={id} />
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
ControlAdapterList.displayName = 'ControlAdapterList';
|
@ -1,99 +0,0 @@
|
||||
import {
|
||||
CompositeNumberInput,
|
||||
CompositeSlider,
|
||||
Flex,
|
||||
FormControl,
|
||||
FormLabel,
|
||||
IconButton,
|
||||
Popover,
|
||||
PopoverArrow,
|
||||
PopoverBody,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
Switch,
|
||||
} from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { stopPropagation } from 'common/util/stopPropagation';
|
||||
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
|
||||
import { caFilterChanged, caOpacityChanged } from 'features/controlLayers/store/canvasV2Slice';
|
||||
import { selectCAOrThrow } from 'features/controlLayers/store/controlAdaptersReducers';
|
||||
import type { ChangeEvent } from 'react';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PiDropHalfFill } from 'react-icons/pi';
|
||||
|
||||
const marks = [0, 25, 50, 75, 100];
|
||||
const formatPct = (v: number | string) => `${v} %`;
|
||||
|
||||
export const ControlAdapterOpacityAndFilter = memo(() => {
|
||||
const { id } = useEntityIdentifierContext();
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
const opacity = useAppSelector((s) => Math.round(selectCAOrThrow(s.canvasV2, id).opacity * 100));
|
||||
const isFilterEnabled = useAppSelector((s) =>
|
||||
selectCAOrThrow(s.canvasV2, id).filters.includes('LightnessToAlphaFilter')
|
||||
);
|
||||
const onChangeOpacity = useCallback(
|
||||
(v: number) => {
|
||||
dispatch(caOpacityChanged({ id, opacity: v / 100 }));
|
||||
},
|
||||
[dispatch, id]
|
||||
);
|
||||
const onChangeFilter = useCallback(
|
||||
(e: ChangeEvent<HTMLInputElement>) => {
|
||||
dispatch(caFilterChanged({ id, filters: e.target.checked ? ['LightnessToAlphaFilter'] : [] }));
|
||||
},
|
||||
[dispatch, id]
|
||||
);
|
||||
return (
|
||||
<Popover isLazy>
|
||||
<PopoverTrigger>
|
||||
<IconButton
|
||||
aria-label={t('controlLayers.opacity')}
|
||||
size="sm"
|
||||
icon={<PiDropHalfFill size={16} />}
|
||||
variant="ghost"
|
||||
onDoubleClick={stopPropagation}
|
||||
/>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent onDoubleClick={stopPropagation}>
|
||||
<PopoverArrow />
|
||||
<PopoverBody>
|
||||
<Flex direction="column" gap={2}>
|
||||
<FormControl orientation="horizontal" w="full">
|
||||
<FormLabel m={0} flexGrow={1} cursor="pointer">
|
||||
{t('controlLayers.opacityFilter')}
|
||||
</FormLabel>
|
||||
<Switch isChecked={isFilterEnabled} onChange={onChangeFilter} />
|
||||
</FormControl>
|
||||
<FormControl orientation="horizontal">
|
||||
<FormLabel m={0}>{t('controlLayers.opacity')}</FormLabel>
|
||||
<CompositeSlider
|
||||
min={0}
|
||||
max={100}
|
||||
step={1}
|
||||
value={opacity}
|
||||
defaultValue={100}
|
||||
onChange={onChangeOpacity}
|
||||
marks={marks}
|
||||
w={48}
|
||||
/>
|
||||
<CompositeNumberInput
|
||||
min={0}
|
||||
max={100}
|
||||
step={1}
|
||||
value={opacity}
|
||||
defaultValue={100}
|
||||
onChange={onChangeOpacity}
|
||||
w={24}
|
||||
format={formatPct}
|
||||
/>
|
||||
</FormControl>
|
||||
</Flex>
|
||||
</PopoverBody>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
);
|
||||
});
|
||||
|
||||
ControlAdapterOpacityAndFilter.displayName = 'ControlAdapterOpacityAndFilter';
|
@ -1,84 +0,0 @@
|
||||
import { CannyProcessor } from 'features/controlLayers/components/ControlAdapter/processors/CannyProcessor';
|
||||
import { ColorMapProcessor } from 'features/controlLayers/components/ControlAdapter/processors/ColorMapProcessor';
|
||||
import { ContentShuffleProcessor } from 'features/controlLayers/components/ControlAdapter/processors/ContentShuffleProcessor';
|
||||
import { DepthAnythingProcessor } from 'features/controlLayers/components/ControlAdapter/processors/DepthAnythingProcessor';
|
||||
import { DWOpenposeProcessor } from 'features/controlLayers/components/ControlAdapter/processors/DWOpenposeProcessor';
|
||||
import { HedProcessor } from 'features/controlLayers/components/ControlAdapter/processors/HedProcessor';
|
||||
import { LineartProcessor } from 'features/controlLayers/components/ControlAdapter/processors/LineartProcessor';
|
||||
import { MediapipeFaceProcessor } from 'features/controlLayers/components/ControlAdapter/processors/MediapipeFaceProcessor';
|
||||
import { MidasDepthProcessor } from 'features/controlLayers/components/ControlAdapter/processors/MidasDepthProcessor';
|
||||
import { MlsdImageProcessor } from 'features/controlLayers/components/ControlAdapter/processors/MlsdImageProcessor';
|
||||
import { PidiProcessor } from 'features/controlLayers/components/ControlAdapter/processors/PidiProcessor';
|
||||
import type { FilterConfig } from 'features/controlLayers/store/types';
|
||||
import { memo } from 'react';
|
||||
|
||||
type Props = {
|
||||
config: FilterConfig | null;
|
||||
onChange: (config: FilterConfig | null) => void;
|
||||
};
|
||||
|
||||
export const ControlAdapterProcessorConfig = memo(({ config, onChange }: Props) => {
|
||||
if (!config) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (config.type === 'canny_image_processor') {
|
||||
return <CannyProcessor onChange={onChange} config={config} />;
|
||||
}
|
||||
|
||||
if (config.type === 'color_map_image_processor') {
|
||||
return <ColorMapProcessor onChange={onChange} config={config} />;
|
||||
}
|
||||
|
||||
if (config.type === 'depth_anything_image_processor') {
|
||||
return <DepthAnythingProcessor onChange={onChange} config={config} />;
|
||||
}
|
||||
|
||||
if (config.type === 'hed_image_processor') {
|
||||
return <HedProcessor onChange={onChange} config={config} />;
|
||||
}
|
||||
|
||||
if (config.type === 'lineart_image_processor') {
|
||||
return <LineartProcessor onChange={onChange} config={config} />;
|
||||
}
|
||||
|
||||
if (config.type === 'content_shuffle_image_processor') {
|
||||
return <ContentShuffleProcessor onChange={onChange} config={config} />;
|
||||
}
|
||||
|
||||
if (config.type === 'lineart_anime_image_processor') {
|
||||
// No configurable options for this processor
|
||||
return null;
|
||||
}
|
||||
|
||||
if (config.type === 'mediapipe_face_processor') {
|
||||
return <MediapipeFaceProcessor onChange={onChange} config={config} />;
|
||||
}
|
||||
|
||||
if (config.type === 'midas_depth_image_processor') {
|
||||
return <MidasDepthProcessor onChange={onChange} config={config} />;
|
||||
}
|
||||
|
||||
if (config.type === 'mlsd_image_processor') {
|
||||
return <MlsdImageProcessor onChange={onChange} config={config} />;
|
||||
}
|
||||
|
||||
if (config.type === 'normalbae_image_processor') {
|
||||
// No configurable options for this processor
|
||||
return null;
|
||||
}
|
||||
|
||||
if (config.type === 'dw_openpose_image_processor') {
|
||||
return <DWOpenposeProcessor onChange={onChange} config={config} />;
|
||||
}
|
||||
|
||||
if (config.type === 'pidi_image_processor') {
|
||||
return <PidiProcessor onChange={onChange} config={config} />;
|
||||
}
|
||||
|
||||
if (config.type === 'zoe_depth_image_processor') {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
ControlAdapterProcessorConfig.displayName = 'ControlAdapterProcessorConfig';
|
@ -1,70 +0,0 @@
|
||||
import type { ComboboxOnChange } from '@invoke-ai/ui-library';
|
||||
import { Combobox, Flex, FormControl, FormLabel, IconButton } from '@invoke-ai/ui-library';
|
||||
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
|
||||
import type {FilterConfig } from 'features/controlLayers/store/types';
|
||||
import { IMAGE_FILTERS, isFilterType } from 'features/controlLayers/store/types';
|
||||
import { configSelector } from 'features/system/store/configSelectors';
|
||||
import { includes, map } from 'lodash-es';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PiXBold } from 'react-icons/pi';
|
||||
import { assert } from 'tsafe';
|
||||
|
||||
type Props = {
|
||||
config: FilterConfig | null;
|
||||
onChange: (config: FilterConfig | null) => void;
|
||||
};
|
||||
|
||||
const selectDisabledProcessors = createMemoizedSelector(
|
||||
configSelector,
|
||||
(config) => config.sd.disabledControlNetProcessors
|
||||
);
|
||||
|
||||
export const ControlAdapterProcessorTypeSelect = memo(({ config, onChange }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const disabledProcessors = useAppSelector(selectDisabledProcessors);
|
||||
const options = useMemo(() => {
|
||||
return map(IMAGE_FILTERS, ({ labelTKey }, type) => ({ value: type, label: t(labelTKey) })).filter(
|
||||
(o) => !includes(disabledProcessors, o.value)
|
||||
);
|
||||
}, [disabledProcessors, t]);
|
||||
|
||||
const _onChange = useCallback<ComboboxOnChange>(
|
||||
(v) => {
|
||||
if (!v) {
|
||||
onChange(null);
|
||||
} else {
|
||||
assert(isFilterType(v.value));
|
||||
onChange(IMAGE_FILTERS[v.value].buildDefaults());
|
||||
}
|
||||
},
|
||||
[onChange]
|
||||
);
|
||||
const clearProcessor = useCallback(() => {
|
||||
onChange(null);
|
||||
}, [onChange]);
|
||||
const value = useMemo(() => options.find((o) => o.value === config?.type) ?? null, [options, config?.type]);
|
||||
|
||||
return (
|
||||
<Flex gap={2}>
|
||||
<FormControl>
|
||||
<InformationalPopover feature="controlNetProcessor">
|
||||
<FormLabel m={0}>{t('controlnet.processor')}</FormLabel>
|
||||
</InformationalPopover>
|
||||
<Combobox value={value} options={options} onChange={_onChange} isSearchable={false} isClearable={false} />
|
||||
</FormControl>
|
||||
<IconButton
|
||||
aria-label={t('controlLayers.clearProcessor')}
|
||||
onClick={clearProcessor}
|
||||
isDisabled={!config}
|
||||
icon={<PiXBold />}
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
/>
|
||||
</Flex>
|
||||
);
|
||||
});
|
||||
|
||||
ControlAdapterProcessorTypeSelect.displayName = 'ControlAdapterProcessorTypeSelect';
|
@ -1,154 +0,0 @@
|
||||
import { Box, Divider, Flex, Icon, IconButton } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { BeginEndStepPct } from 'features/controlLayers/components/common/BeginEndStepPct';
|
||||
import { CanvasEntitySettings } from 'features/controlLayers/components/common/CanvasEntitySettings';
|
||||
import { Weight } from 'features/controlLayers/components/common/Weight';
|
||||
import { ControlAdapterControlModeSelect } from 'features/controlLayers/components/ControlAdapter/ControlAdapterControlModeSelect';
|
||||
import { ControlAdapterImagePreview } from 'features/controlLayers/components/ControlAdapter/ControlAdapterImagePreview';
|
||||
import { ControlAdapterModel } from 'features/controlLayers/components/ControlAdapter/ControlAdapterModel';
|
||||
import { ControlAdapterProcessorConfig } from 'features/controlLayers/components/ControlAdapter/ControlAdapterProcessorConfig';
|
||||
import { ControlAdapterProcessorTypeSelect } from 'features/controlLayers/components/ControlAdapter/ControlAdapterProcessorTypeSelect';
|
||||
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
|
||||
import {
|
||||
caBeginEndStepPctChanged,
|
||||
caControlModeChanged,
|
||||
caImageChanged,
|
||||
caModelChanged,
|
||||
caProcessedImageChanged,
|
||||
caProcessorConfigChanged,
|
||||
caWeightChanged,
|
||||
} from 'features/controlLayers/store/canvasV2Slice';
|
||||
import { selectCAOrThrow } from 'features/controlLayers/store/controlAdaptersReducers';
|
||||
import type { ControlModeV2, FilterConfig } from 'features/controlLayers/store/types';
|
||||
import type { CAImageDropData } from 'features/dnd/types';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PiCaretUpBold } from 'react-icons/pi';
|
||||
import { useToggle } from 'react-use';
|
||||
import type {
|
||||
CAImagePostUploadAction,
|
||||
ControlNetModelConfig,
|
||||
ImageDTO,
|
||||
T2IAdapterModelConfig,
|
||||
} from 'services/api/types';
|
||||
|
||||
export const ControlAdapterSettings = memo(() => {
|
||||
const { id } = useEntityIdentifierContext();
|
||||
const dispatch = useAppDispatch();
|
||||
const { t } = useTranslation();
|
||||
const [isExpanded, toggleIsExpanded] = useToggle(false);
|
||||
|
||||
const controlAdapter = useAppSelector((s) => selectCAOrThrow(s.canvasV2, id));
|
||||
|
||||
const onChangeBeginEndStepPct = useCallback(
|
||||
(beginEndStepPct: [number, number]) => {
|
||||
dispatch(caBeginEndStepPctChanged({ id, beginEndStepPct }));
|
||||
},
|
||||
[dispatch, id]
|
||||
);
|
||||
|
||||
const onChangeControlMode = useCallback(
|
||||
(controlMode: ControlModeV2) => {
|
||||
dispatch(caControlModeChanged({ id, controlMode }));
|
||||
},
|
||||
[dispatch, id]
|
||||
);
|
||||
|
||||
const onChangeWeight = useCallback(
|
||||
(weight: number) => {
|
||||
dispatch(caWeightChanged({ id, weight }));
|
||||
},
|
||||
[dispatch, id]
|
||||
);
|
||||
|
||||
const onChangeProcessorConfig = useCallback(
|
||||
(processorConfig: FilterConfig | null) => {
|
||||
dispatch(caProcessorConfigChanged({ id, processorConfig }));
|
||||
},
|
||||
[dispatch, id]
|
||||
);
|
||||
|
||||
const onChangeModel = useCallback(
|
||||
(modelConfig: ControlNetModelConfig | T2IAdapterModelConfig) => {
|
||||
dispatch(caModelChanged({ id, modelConfig }));
|
||||
},
|
||||
[dispatch, id]
|
||||
);
|
||||
|
||||
const onChangeImage = useCallback(
|
||||
(imageDTO: ImageDTO | null) => {
|
||||
dispatch(caImageChanged({ id, imageDTO }));
|
||||
},
|
||||
[dispatch, id]
|
||||
);
|
||||
|
||||
const onErrorLoadingImage = useCallback(() => {
|
||||
dispatch(caImageChanged({ id, imageDTO: null }));
|
||||
}, [dispatch, id]);
|
||||
|
||||
const onErrorLoadingProcessedImage = useCallback(() => {
|
||||
dispatch(caProcessedImageChanged({ id, imageDTO: null }));
|
||||
}, [dispatch, id]);
|
||||
|
||||
const droppableData = useMemo<CAImageDropData>(() => ({ actionType: 'SET_CA_IMAGE', context: { id }, id }), [id]);
|
||||
const postUploadAction = useMemo<CAImagePostUploadAction>(() => ({ id, type: 'SET_CA_IMAGE' }), [id]);
|
||||
|
||||
return (
|
||||
<CanvasEntitySettings>
|
||||
<Flex flexDir="column" gap={3} position="relative" w="full">
|
||||
<Flex gap={3} alignItems="center" w="full">
|
||||
<Box minW={0} w="full" transitionProperty="common" transitionDuration="0.1s">
|
||||
<ControlAdapterModel modelKey={controlAdapter.model?.key ?? null} onChange={onChangeModel} />
|
||||
</Box>
|
||||
|
||||
<IconButton
|
||||
size="sm"
|
||||
tooltip={isExpanded ? t('controlnet.hideAdvanced') : t('controlnet.showAdvanced')}
|
||||
aria-label={isExpanded ? t('controlnet.hideAdvanced') : t('controlnet.showAdvanced')}
|
||||
onClick={toggleIsExpanded}
|
||||
variant="ghost"
|
||||
icon={
|
||||
<Icon
|
||||
boxSize={4}
|
||||
as={PiCaretUpBold}
|
||||
transform={isExpanded ? 'rotate(0deg)' : 'rotate(180deg)'}
|
||||
transitionProperty="common"
|
||||
transitionDuration="normal"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</Flex>
|
||||
<Flex gap={3} w="full">
|
||||
<Flex flexDir="column" gap={3} w="full" h="full">
|
||||
{controlAdapter.adapterType === 'controlnet' && (
|
||||
<ControlAdapterControlModeSelect controlMode={controlAdapter.controlMode} onChange={onChangeControlMode} />
|
||||
)}
|
||||
<Weight weight={controlAdapter.weight} onChange={onChangeWeight} />
|
||||
<BeginEndStepPct beginEndStepPct={controlAdapter.beginEndStepPct} onChange={onChangeBeginEndStepPct} />
|
||||
</Flex>
|
||||
<Flex alignItems="center" justifyContent="center" h={36} w={36} aspectRatio="1/1">
|
||||
<ControlAdapterImagePreview
|
||||
controlAdapter={controlAdapter}
|
||||
onChangeImage={onChangeImage}
|
||||
droppableData={droppableData}
|
||||
postUploadAction={postUploadAction}
|
||||
onErrorLoadingImage={onErrorLoadingImage}
|
||||
onErrorLoadingProcessedImage={onErrorLoadingProcessedImage}
|
||||
/>
|
||||
</Flex>
|
||||
</Flex>
|
||||
{isExpanded && (
|
||||
<>
|
||||
<Divider />
|
||||
<Flex flexDir="column" gap={3} w="full">
|
||||
<ControlAdapterProcessorTypeSelect config={controlAdapter.processorConfig} onChange={onChangeProcessorConfig} />
|
||||
<ControlAdapterProcessorConfig config={controlAdapter.processorConfig} onChange={onChangeProcessorConfig} />
|
||||
</Flex>
|
||||
</>
|
||||
)}
|
||||
</Flex>
|
||||
</CanvasEntitySettings>
|
||||
);
|
||||
});
|
||||
|
||||
ControlAdapterSettings.displayName = 'ControlAdapterSettings';
|
@ -1,68 +0,0 @@
|
||||
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||
import type { ProcessorComponentProps } from 'features/controlLayers/components/ControlAdapter/processors/types';
|
||||
import type { CannyProcessorConfig } from 'features/controlLayers/store/types';
|
||||
import { IMAGE_FILTERS } from 'features/controlLayers/store/types';
|
||||
import { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import ProcessorWrapper from './ProcessorWrapper';
|
||||
|
||||
type Props = ProcessorComponentProps<CannyProcessorConfig>;
|
||||
const DEFAULTS = IMAGE_FILTERS['canny_image_processor'].buildDefaults();
|
||||
|
||||
export const CannyProcessor = ({ onChange, config }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const handleLowThresholdChanged = useCallback(
|
||||
(v: number) => {
|
||||
onChange({ ...config, low_threshold: v });
|
||||
},
|
||||
[onChange, config]
|
||||
);
|
||||
const handleHighThresholdChanged = useCallback(
|
||||
(v: number) => {
|
||||
onChange({ ...config, high_threshold: v });
|
||||
},
|
||||
[onChange, config]
|
||||
);
|
||||
|
||||
return (
|
||||
<ProcessorWrapper>
|
||||
<FormControl>
|
||||
<FormLabel m={0}>{t('controlnet.lowThreshold')}</FormLabel>
|
||||
<CompositeSlider
|
||||
value={config.low_threshold}
|
||||
onChange={handleLowThresholdChanged}
|
||||
defaultValue={DEFAULTS.low_threshold}
|
||||
min={0}
|
||||
max={255}
|
||||
/>
|
||||
<CompositeNumberInput
|
||||
value={config.low_threshold}
|
||||
onChange={handleLowThresholdChanged}
|
||||
defaultValue={DEFAULTS.low_threshold}
|
||||
min={0}
|
||||
max={255}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormControl>
|
||||
<FormLabel m={0}>{t('controlnet.highThreshold')}</FormLabel>
|
||||
<CompositeSlider
|
||||
value={config.high_threshold}
|
||||
onChange={handleHighThresholdChanged}
|
||||
defaultValue={DEFAULTS.high_threshold}
|
||||
min={0}
|
||||
max={255}
|
||||
/>
|
||||
<CompositeNumberInput
|
||||
value={config.high_threshold}
|
||||
onChange={handleHighThresholdChanged}
|
||||
defaultValue={DEFAULTS.high_threshold}
|
||||
min={0}
|
||||
max={255}
|
||||
/>
|
||||
</FormControl>
|
||||
</ProcessorWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
CannyProcessor.displayName = 'CannyProcessor';
|
@ -1,48 +0,0 @@
|
||||
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||
import type { ProcessorComponentProps } from 'features/controlLayers/components/ControlAdapter/processors/types';
|
||||
import type { ColorMapProcessorConfig } from 'features/controlLayers/store/types';
|
||||
import { IMAGE_FILTERS } from 'features/controlLayers/store/types';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import ProcessorWrapper from './ProcessorWrapper';
|
||||
|
||||
type Props = ProcessorComponentProps<ColorMapProcessorConfig>;
|
||||
const DEFAULTS = IMAGE_FILTERS['color_map_image_processor'].buildDefaults();
|
||||
|
||||
export const ColorMapProcessor = memo(({ onChange, config }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const handleColorMapTileSizeChanged = useCallback(
|
||||
(v: number) => {
|
||||
onChange({ ...config, color_map_tile_size: v });
|
||||
},
|
||||
[config, onChange]
|
||||
);
|
||||
|
||||
return (
|
||||
<ProcessorWrapper>
|
||||
<FormControl>
|
||||
<FormLabel m={0}>{t('controlnet.colorMapTileSize')}</FormLabel>
|
||||
<CompositeSlider
|
||||
value={config.color_map_tile_size}
|
||||
defaultValue={DEFAULTS.color_map_tile_size}
|
||||
onChange={handleColorMapTileSizeChanged}
|
||||
min={1}
|
||||
max={256}
|
||||
step={1}
|
||||
marks
|
||||
/>
|
||||
<CompositeNumberInput
|
||||
value={config.color_map_tile_size}
|
||||
defaultValue={DEFAULTS.color_map_tile_size}
|
||||
onChange={handleColorMapTileSizeChanged}
|
||||
min={1}
|
||||
max={4096}
|
||||
step={1}
|
||||
/>
|
||||
</FormControl>
|
||||
</ProcessorWrapper>
|
||||
);
|
||||
});
|
||||
|
||||
ColorMapProcessor.displayName = 'ColorMapProcessor';
|
@ -1,79 +0,0 @@
|
||||
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||
import type { ProcessorComponentProps } from 'features/controlLayers/components/ControlAdapter/processors/types';
|
||||
import type { ContentShuffleProcessorConfig } from 'features/controlLayers/store/types';
|
||||
import { IMAGE_FILTERS } from 'features/controlLayers/store/types';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import ProcessorWrapper from './ProcessorWrapper';
|
||||
|
||||
type Props = ProcessorComponentProps<ContentShuffleProcessorConfig>;
|
||||
const DEFAULTS = IMAGE_FILTERS['content_shuffle_image_processor'].buildDefaults();
|
||||
|
||||
export const ContentShuffleProcessor = memo(({ onChange, config }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleWChanged = useCallback(
|
||||
(v: number) => {
|
||||
onChange({ ...config, w: v });
|
||||
},
|
||||
[config, onChange]
|
||||
);
|
||||
|
||||
const handleHChanged = useCallback(
|
||||
(v: number) => {
|
||||
onChange({ ...config, h: v });
|
||||
},
|
||||
[config, onChange]
|
||||
);
|
||||
|
||||
const handleFChanged = useCallback(
|
||||
(v: number) => {
|
||||
onChange({ ...config, f: v });
|
||||
},
|
||||
[config, onChange]
|
||||
);
|
||||
|
||||
return (
|
||||
<ProcessorWrapper>
|
||||
<FormControl>
|
||||
<FormLabel m={0}>{t('controlnet.w')}</FormLabel>
|
||||
<CompositeSlider
|
||||
value={config.w}
|
||||
defaultValue={DEFAULTS.w}
|
||||
onChange={handleWChanged}
|
||||
min={0}
|
||||
max={4096}
|
||||
marks
|
||||
/>
|
||||
<CompositeNumberInput value={config.w} defaultValue={DEFAULTS.w} onChange={handleWChanged} min={0} max={4096} />
|
||||
</FormControl>
|
||||
<FormControl>
|
||||
<FormLabel m={0}>{t('controlnet.h')}</FormLabel>
|
||||
<CompositeSlider
|
||||
value={config.h}
|
||||
defaultValue={DEFAULTS.h}
|
||||
onChange={handleHChanged}
|
||||
min={0}
|
||||
max={4096}
|
||||
marks
|
||||
/>
|
||||
<CompositeNumberInput value={config.h} defaultValue={DEFAULTS.h} onChange={handleHChanged} min={0} max={4096} />
|
||||
</FormControl>
|
||||
<FormControl>
|
||||
<FormLabel m={0}>{t('controlnet.f')}</FormLabel>
|
||||
<CompositeSlider
|
||||
value={config.f}
|
||||
defaultValue={DEFAULTS.f}
|
||||
onChange={handleFChanged}
|
||||
min={0}
|
||||
max={4096}
|
||||
marks
|
||||
/>
|
||||
<CompositeNumberInput value={config.f} defaultValue={DEFAULTS.f} onChange={handleFChanged} min={0} max={4096} />
|
||||
</FormControl>
|
||||
</ProcessorWrapper>
|
||||
);
|
||||
});
|
||||
|
||||
ContentShuffleProcessor.displayName = 'ContentShuffleProcessor';
|
@ -1,62 +0,0 @@
|
||||
import { Flex, FormControl, FormLabel, Switch } from '@invoke-ai/ui-library';
|
||||
import type { ProcessorComponentProps } from 'features/controlLayers/components/ControlAdapter/processors/types';
|
||||
import type { DWOpenposeProcessorConfig } from 'features/controlLayers/store/types';
|
||||
import { IMAGE_FILTERS } from 'features/controlLayers/store/types';
|
||||
import type { ChangeEvent } from 'react';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import ProcessorWrapper from './ProcessorWrapper';
|
||||
|
||||
type Props = ProcessorComponentProps<DWOpenposeProcessorConfig>;
|
||||
const DEFAULTS = IMAGE_FILTERS['dw_openpose_image_processor'].buildDefaults();
|
||||
|
||||
export const DWOpenposeProcessor = memo(({ onChange, config }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleDrawBodyChanged = useCallback(
|
||||
(e: ChangeEvent<HTMLInputElement>) => {
|
||||
onChange({ ...config, draw_body: e.target.checked });
|
||||
},
|
||||
[config, onChange]
|
||||
);
|
||||
|
||||
const handleDrawFaceChanged = useCallback(
|
||||
(e: ChangeEvent<HTMLInputElement>) => {
|
||||
onChange({ ...config, draw_face: e.target.checked });
|
||||
},
|
||||
[config, onChange]
|
||||
);
|
||||
|
||||
const handleDrawHandsChanged = useCallback(
|
||||
(e: ChangeEvent<HTMLInputElement>) => {
|
||||
onChange({ ...config, draw_hands: e.target.checked });
|
||||
},
|
||||
[config, onChange]
|
||||
);
|
||||
|
||||
return (
|
||||
<ProcessorWrapper>
|
||||
<Flex sx={{ flexDir: 'row', gap: 6 }}>
|
||||
<FormControl w="max-content">
|
||||
<FormLabel m={0}>{t('controlnet.body')}</FormLabel>
|
||||
<Switch defaultChecked={DEFAULTS.draw_body} isChecked={config.draw_body} onChange={handleDrawBodyChanged} />
|
||||
</FormControl>
|
||||
<FormControl w="max-content">
|
||||
<FormLabel m={0}>{t('controlnet.face')}</FormLabel>
|
||||
<Switch defaultChecked={DEFAULTS.draw_face} isChecked={config.draw_face} onChange={handleDrawFaceChanged} />
|
||||
</FormControl>
|
||||
<FormControl w="max-content">
|
||||
<FormLabel m={0}>{t('controlnet.hands')}</FormLabel>
|
||||
<Switch
|
||||
defaultChecked={DEFAULTS.draw_hands}
|
||||
isChecked={config.draw_hands}
|
||||
onChange={handleDrawHandsChanged}
|
||||
/>
|
||||
</FormControl>
|
||||
</Flex>
|
||||
</ProcessorWrapper>
|
||||
);
|
||||
});
|
||||
|
||||
DWOpenposeProcessor.displayName = 'DWOpenposeProcessor';
|
@ -1,47 +0,0 @@
|
||||
import type { ComboboxOnChange } from '@invoke-ai/ui-library';
|
||||
import { Combobox, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||
import type { ProcessorComponentProps } from 'features/controlLayers/components/ControlAdapter/processors/types';
|
||||
import type { DepthAnythingModelSize, DepthAnythingProcessorConfig } from 'features/controlLayers/store/types';
|
||||
import { isDepthAnythingModelSize } from 'features/controlLayers/store/types';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import ProcessorWrapper from './ProcessorWrapper';
|
||||
|
||||
type Props = ProcessorComponentProps<DepthAnythingProcessorConfig>;
|
||||
|
||||
export const DepthAnythingProcessor = memo(({ onChange, config }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const handleModelSizeChange = useCallback<ComboboxOnChange>(
|
||||
(v) => {
|
||||
if (!isDepthAnythingModelSize(v?.value)) {
|
||||
return;
|
||||
}
|
||||
onChange({ ...config, model_size: v.value });
|
||||
},
|
||||
[config, onChange]
|
||||
);
|
||||
|
||||
const options: { label: string; value: DepthAnythingModelSize }[] = useMemo(
|
||||
() => [
|
||||
{ label: t('controlnet.depthAnythingSmallV2'), value: 'small_v2' },
|
||||
{ label: t('controlnet.small'), value: 'small' },
|
||||
{ label: t('controlnet.base'), value: 'base' },
|
||||
{ label: t('controlnet.large'), value: 'large' },
|
||||
],
|
||||
[t]
|
||||
);
|
||||
|
||||
const value = useMemo(() => options.filter((o) => o.value === config.model_size)[0], [options, config.model_size]);
|
||||
|
||||
return (
|
||||
<ProcessorWrapper>
|
||||
<FormControl>
|
||||
<FormLabel m={0}>{t('controlnet.modelSize')}</FormLabel>
|
||||
<Combobox value={value} options={options} onChange={handleModelSizeChange} isSearchable={false} />
|
||||
</FormControl>
|
||||
</ProcessorWrapper>
|
||||
);
|
||||
});
|
||||
|
||||
DepthAnythingProcessor.displayName = 'DepthAnythingProcessor';
|
@ -1,32 +0,0 @@
|
||||
import { FormControl, FormLabel, Switch } from '@invoke-ai/ui-library';
|
||||
import type { ProcessorComponentProps } from 'features/controlLayers/components/ControlAdapter/processors/types';
|
||||
import type { HedProcessorConfig } from 'features/controlLayers/store/types';
|
||||
import type { ChangeEvent } from 'react';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import ProcessorWrapper from './ProcessorWrapper';
|
||||
|
||||
type Props = ProcessorComponentProps<HedProcessorConfig>;
|
||||
|
||||
export const HedProcessor = memo(({ onChange, config }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleScribbleChanged = useCallback(
|
||||
(e: ChangeEvent<HTMLInputElement>) => {
|
||||
onChange({ ...config, scribble: e.target.checked });
|
||||
},
|
||||
[config, onChange]
|
||||
);
|
||||
|
||||
return (
|
||||
<ProcessorWrapper>
|
||||
<FormControl>
|
||||
<FormLabel m={0}>{t('controlnet.scribble')}</FormLabel>
|
||||
<Switch isChecked={config.scribble} onChange={handleScribbleChanged} />
|
||||
</FormControl>
|
||||
</ProcessorWrapper>
|
||||
);
|
||||
});
|
||||
|
||||
HedProcessor.displayName = 'HedProcessor';
|
@ -1,32 +0,0 @@
|
||||
import { FormControl, FormLabel, Switch } from '@invoke-ai/ui-library';
|
||||
import type { ProcessorComponentProps } from 'features/controlLayers/components/ControlAdapter/processors/types';
|
||||
import type { LineartProcessorConfig } from 'features/controlLayers/store/types';
|
||||
import type { ChangeEvent } from 'react';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import ProcessorWrapper from './ProcessorWrapper';
|
||||
|
||||
type Props = ProcessorComponentProps<LineartProcessorConfig>;
|
||||
|
||||
export const LineartProcessor = memo(({ onChange, config }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleCoarseChanged = useCallback(
|
||||
(e: ChangeEvent<HTMLInputElement>) => {
|
||||
onChange({ ...config, coarse: e.target.checked });
|
||||
},
|
||||
[config, onChange]
|
||||
);
|
||||
|
||||
return (
|
||||
<ProcessorWrapper>
|
||||
<FormControl>
|
||||
<FormLabel m={0}>{t('controlnet.coarse')}</FormLabel>
|
||||
<Switch isChecked={config.coarse} onChange={handleCoarseChanged} />
|
||||
</FormControl>
|
||||
</ProcessorWrapper>
|
||||
);
|
||||
});
|
||||
|
||||
LineartProcessor.displayName = 'LineartProcessor';
|
@ -1,74 +0,0 @@
|
||||
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||
import type { ProcessorComponentProps } from 'features/controlLayers/components/ControlAdapter/processors/types';
|
||||
import type { MediapipeFaceProcessorConfig } from 'features/controlLayers/store/types';
|
||||
import { IMAGE_FILTERS } from 'features/controlLayers/store/types';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import ProcessorWrapper from './ProcessorWrapper';
|
||||
|
||||
type Props = ProcessorComponentProps<MediapipeFaceProcessorConfig>;
|
||||
const DEFAULTS = IMAGE_FILTERS['mediapipe_face_processor'].buildDefaults();
|
||||
|
||||
export const MediapipeFaceProcessor = memo(({ onChange, config }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleMaxFacesChanged = useCallback(
|
||||
(v: number) => {
|
||||
onChange({ ...config, max_faces: v });
|
||||
},
|
||||
[config, onChange]
|
||||
);
|
||||
|
||||
const handleMinConfidenceChanged = useCallback(
|
||||
(v: number) => {
|
||||
onChange({ ...config, min_confidence: v });
|
||||
},
|
||||
[config, onChange]
|
||||
);
|
||||
|
||||
return (
|
||||
<ProcessorWrapper>
|
||||
<FormControl>
|
||||
<FormLabel m={0}>{t('controlnet.maxFaces')}</FormLabel>
|
||||
<CompositeSlider
|
||||
value={config.max_faces}
|
||||
onChange={handleMaxFacesChanged}
|
||||
defaultValue={DEFAULTS.max_faces}
|
||||
min={1}
|
||||
max={20}
|
||||
marks
|
||||
/>
|
||||
<CompositeNumberInput
|
||||
value={config.max_faces}
|
||||
onChange={handleMaxFacesChanged}
|
||||
defaultValue={DEFAULTS.max_faces}
|
||||
min={1}
|
||||
max={20}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormControl>
|
||||
<FormLabel m={0}>{t('controlnet.minConfidence')}</FormLabel>
|
||||
<CompositeSlider
|
||||
value={config.min_confidence}
|
||||
onChange={handleMinConfidenceChanged}
|
||||
defaultValue={DEFAULTS.min_confidence}
|
||||
min={0}
|
||||
max={1}
|
||||
step={0.01}
|
||||
marks
|
||||
/>
|
||||
<CompositeNumberInput
|
||||
value={config.min_confidence}
|
||||
onChange={handleMinConfidenceChanged}
|
||||
defaultValue={DEFAULTS.min_confidence}
|
||||
min={0}
|
||||
max={1}
|
||||
step={0.01}
|
||||
/>
|
||||
</FormControl>
|
||||
</ProcessorWrapper>
|
||||
);
|
||||
});
|
||||
|
||||
MediapipeFaceProcessor.displayName = 'MediapipeFaceProcessor';
|
@ -1,76 +0,0 @@
|
||||
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||
import type { ProcessorComponentProps } from 'features/controlLayers/components/ControlAdapter/processors/types';
|
||||
import type { MidasDepthProcessorConfig } from 'features/controlLayers/store/types';
|
||||
import { IMAGE_FILTERS } from 'features/controlLayers/store/types';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import ProcessorWrapper from './ProcessorWrapper';
|
||||
|
||||
type Props = ProcessorComponentProps<MidasDepthProcessorConfig>;
|
||||
const DEFAULTS = IMAGE_FILTERS['midas_depth_image_processor'].buildDefaults();
|
||||
|
||||
export const MidasDepthProcessor = memo(({ onChange, config }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleAMultChanged = useCallback(
|
||||
(v: number) => {
|
||||
onChange({ ...config, a_mult: v });
|
||||
},
|
||||
[config, onChange]
|
||||
);
|
||||
|
||||
const handleBgThChanged = useCallback(
|
||||
(v: number) => {
|
||||
onChange({ ...config, bg_th: v });
|
||||
},
|
||||
[config, onChange]
|
||||
);
|
||||
|
||||
return (
|
||||
<ProcessorWrapper>
|
||||
<FormControl>
|
||||
<FormLabel m={0}>{t('controlnet.amult')}</FormLabel>
|
||||
<CompositeSlider
|
||||
value={config.a_mult}
|
||||
onChange={handleAMultChanged}
|
||||
defaultValue={DEFAULTS.a_mult}
|
||||
min={0}
|
||||
max={20}
|
||||
step={0.01}
|
||||
marks
|
||||
/>
|
||||
<CompositeNumberInput
|
||||
value={config.a_mult}
|
||||
onChange={handleAMultChanged}
|
||||
defaultValue={DEFAULTS.a_mult}
|
||||
min={0}
|
||||
max={20}
|
||||
step={0.01}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormControl>
|
||||
<FormLabel m={0}>{t('controlnet.bgth')}</FormLabel>
|
||||
<CompositeSlider
|
||||
value={config.bg_th}
|
||||
onChange={handleBgThChanged}
|
||||
defaultValue={DEFAULTS.bg_th}
|
||||
min={0}
|
||||
max={20}
|
||||
step={0.01}
|
||||
marks
|
||||
/>
|
||||
<CompositeNumberInput
|
||||
value={config.bg_th}
|
||||
onChange={handleBgThChanged}
|
||||
defaultValue={DEFAULTS.bg_th}
|
||||
min={0}
|
||||
max={20}
|
||||
step={0.01}
|
||||
/>
|
||||
</FormControl>
|
||||
</ProcessorWrapper>
|
||||
);
|
||||
});
|
||||
|
||||
MidasDepthProcessor.displayName = 'MidasDepthProcessor';
|
@ -1,76 +0,0 @@
|
||||
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||
import type { ProcessorComponentProps } from 'features/controlLayers/components/ControlAdapter/processors/types';
|
||||
import type { MlsdProcessorConfig } from 'features/controlLayers/store/types';
|
||||
import { IMAGE_FILTERS } from 'features/controlLayers/store/types';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import ProcessorWrapper from './ProcessorWrapper';
|
||||
|
||||
type Props = ProcessorComponentProps<MlsdProcessorConfig>;
|
||||
const DEFAULTS = IMAGE_FILTERS['mlsd_image_processor'].buildDefaults();
|
||||
|
||||
export const MlsdImageProcessor = memo(({ onChange, config }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleThrDChanged = useCallback(
|
||||
(v: number) => {
|
||||
onChange({ ...config, thr_d: v });
|
||||
},
|
||||
[config, onChange]
|
||||
);
|
||||
|
||||
const handleThrVChanged = useCallback(
|
||||
(v: number) => {
|
||||
onChange({ ...config, thr_v: v });
|
||||
},
|
||||
[config, onChange]
|
||||
);
|
||||
|
||||
return (
|
||||
<ProcessorWrapper>
|
||||
<FormControl>
|
||||
<FormLabel m={0}>{t('controlnet.w')} </FormLabel>
|
||||
<CompositeSlider
|
||||
value={config.thr_d}
|
||||
onChange={handleThrDChanged}
|
||||
defaultValue={DEFAULTS.thr_d}
|
||||
min={0}
|
||||
max={1}
|
||||
step={0.01}
|
||||
marks
|
||||
/>
|
||||
<CompositeNumberInput
|
||||
value={config.thr_d}
|
||||
onChange={handleThrDChanged}
|
||||
defaultValue={DEFAULTS.thr_d}
|
||||
min={0}
|
||||
max={1}
|
||||
step={0.01}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormControl>
|
||||
<FormLabel m={0}>{t('controlnet.h')} </FormLabel>
|
||||
<CompositeSlider
|
||||
value={config.thr_v}
|
||||
onChange={handleThrVChanged}
|
||||
defaultValue={DEFAULTS.thr_v}
|
||||
min={0}
|
||||
max={1}
|
||||
step={0.01}
|
||||
marks
|
||||
/>
|
||||
<CompositeNumberInput
|
||||
value={config.thr_v}
|
||||
onChange={handleThrVChanged}
|
||||
defaultValue={DEFAULTS.thr_v}
|
||||
min={0}
|
||||
max={1}
|
||||
step={0.01}
|
||||
/>
|
||||
</FormControl>
|
||||
</ProcessorWrapper>
|
||||
);
|
||||
});
|
||||
|
||||
MlsdImageProcessor.displayName = 'MlsdImageProcessor';
|
@ -1,43 +0,0 @@
|
||||
import { FormControl, FormLabel, Switch } from '@invoke-ai/ui-library';
|
||||
import type { ProcessorComponentProps } from 'features/controlLayers/components/ControlAdapter/processors/types';
|
||||
import type { PidiProcessorConfig } from 'features/controlLayers/store/types';
|
||||
import type { ChangeEvent } from 'react';
|
||||
import { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import ProcessorWrapper from './ProcessorWrapper';
|
||||
|
||||
type Props = ProcessorComponentProps<PidiProcessorConfig>;
|
||||
|
||||
export const PidiProcessor = ({ onChange, config }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleScribbleChanged = useCallback(
|
||||
(e: ChangeEvent<HTMLInputElement>) => {
|
||||
onChange({ ...config, scribble: e.target.checked });
|
||||
},
|
||||
[config, onChange]
|
||||
);
|
||||
|
||||
const handleSafeChanged = useCallback(
|
||||
(e: ChangeEvent<HTMLInputElement>) => {
|
||||
onChange({ ...config, safe: e.target.checked });
|
||||
},
|
||||
[config, onChange]
|
||||
);
|
||||
|
||||
return (
|
||||
<ProcessorWrapper>
|
||||
<FormControl>
|
||||
<FormLabel m={0}>{t('controlnet.scribble')}</FormLabel>
|
||||
<Switch isChecked={config.scribble} onChange={handleScribbleChanged} />
|
||||
</FormControl>
|
||||
<FormControl>
|
||||
<FormLabel m={0}>{t('controlnet.safe')}</FormLabel>
|
||||
<Switch isChecked={config.safe} onChange={handleSafeChanged} />
|
||||
</FormControl>
|
||||
</ProcessorWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
PidiProcessor.displayName = 'PidiProcessor';
|
@ -1,15 +0,0 @@
|
||||
import { Flex } from '@invoke-ai/ui-library';
|
||||
import type { PropsWithChildren } from 'react';
|
||||
import { memo } from 'react';
|
||||
|
||||
type Props = PropsWithChildren;
|
||||
|
||||
const ProcessorWrapper = (props: Props) => {
|
||||
return (
|
||||
<Flex flexDir="column" gap={3}>
|
||||
{props.children}
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(ProcessorWrapper);
|
@ -1,6 +0,0 @@
|
||||
import type { FilterConfig } from 'features/controlLayers/store/types';
|
||||
|
||||
export type ProcessorComponentProps<T extends FilterConfig> = {
|
||||
onChange: (config: T) => void;
|
||||
config: T;
|
||||
};
|
@ -1,4 +1,5 @@
|
||||
import {
|
||||
Button,
|
||||
Checkbox,
|
||||
Flex,
|
||||
FormControl,
|
||||
@ -11,7 +12,11 @@ import {
|
||||
} from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { MaskOpacity } from 'features/controlLayers/components/MaskOpacity';
|
||||
import { clipToBboxChanged, invertScrollChanged } from 'features/controlLayers/store/canvasV2Slice';
|
||||
import {
|
||||
clipToBboxChanged,
|
||||
invertScrollChanged,
|
||||
rasterizationCachesInvalidated,
|
||||
} from 'features/controlLayers/store/canvasV2Slice';
|
||||
import type { ChangeEvent } from 'react';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@ -30,6 +35,9 @@ const ControlLayersSettingsPopover = () => {
|
||||
(e: ChangeEvent<HTMLInputElement>) => dispatch(clipToBboxChanged(e.target.checked)),
|
||||
[dispatch]
|
||||
);
|
||||
const invalidateRasterizationCaches = useCallback(() => {
|
||||
dispatch(rasterizationCachesInvalidated());
|
||||
}, [dispatch]);
|
||||
return (
|
||||
<Popover isLazy>
|
||||
<PopoverTrigger>
|
||||
@ -47,6 +55,9 @@ const ControlLayersSettingsPopover = () => {
|
||||
<FormLabel flexGrow={1}>{t('unifiedCanvas.clipToBbox')}</FormLabel>
|
||||
<Checkbox isChecked={clipToBbox} onChange={onChangeClipToBbox} />
|
||||
</FormControl>
|
||||
<Button onClick={invalidateRasterizationCaches} size="sm">
|
||||
Invalidate Rasterization Caches
|
||||
</Button>
|
||||
</Flex>
|
||||
</PopoverBody>
|
||||
</PopoverContent>
|
||||
|
@ -11,7 +11,7 @@ export const DeleteAllLayersButton = memo(() => {
|
||||
const entityCount = useAppSelector((s) => {
|
||||
return (
|
||||
s.canvasV2.regions.entities.length +
|
||||
s.canvasV2.controlAdapters.entities.length +
|
||||
// s.canvasV2.controlAdapters.entities.length +
|
||||
s.canvasV2.ipAdapters.entities.length +
|
||||
s.canvasV2.layers.entities.length
|
||||
);
|
||||
|
@ -10,8 +10,6 @@ import { EntityIdentifierContext } from 'features/controlLayers/contexts/EntityI
|
||||
import type { CanvasEntityIdentifier } from 'features/controlLayers/store/types';
|
||||
import { memo, useMemo } from 'react';
|
||||
|
||||
import { LayerOpacity } from './LayerOpacity';
|
||||
|
||||
type Props = {
|
||||
id: string;
|
||||
};
|
||||
@ -26,7 +24,6 @@ export const Layer = memo(({ id }: Props) => {
|
||||
<CanvasEntityEnabledToggle />
|
||||
<CanvasEntityTitle />
|
||||
<Spacer />
|
||||
<LayerOpacity />
|
||||
<LayerActionsMenu />
|
||||
<CanvasEntityDeleteButton />
|
||||
</CanvasEntityHeader>
|
||||
|
@ -1,82 +0,0 @@
|
||||
import {
|
||||
CompositeNumberInput,
|
||||
CompositeSlider,
|
||||
Flex,
|
||||
FormControl,
|
||||
FormLabel,
|
||||
IconButton,
|
||||
Popover,
|
||||
PopoverArrow,
|
||||
PopoverBody,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { stopPropagation } from 'common/util/stopPropagation';
|
||||
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
|
||||
import { layerOpacityChanged } from 'features/controlLayers/store/canvasV2Slice';
|
||||
import { selectLayerOrThrow } from 'features/controlLayers/store/layersReducers';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PiDropHalfFill } from 'react-icons/pi';
|
||||
|
||||
const marks = [0, 25, 50, 75, 100];
|
||||
const formatPct = (v: number | string) => `${v} %`;
|
||||
|
||||
export const LayerOpacity = memo(() => {
|
||||
const entityIdentifier = useEntityIdentifierContext();
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
const opacity = useAppSelector((s) => Math.round(selectLayerOrThrow(s.canvasV2, entityIdentifier.id).opacity * 100));
|
||||
const onChangeOpacity = useCallback(
|
||||
(v: number) => {
|
||||
dispatch(layerOpacityChanged({ id: entityIdentifier.id, opacity: v / 100 }));
|
||||
},
|
||||
[dispatch, entityIdentifier.id]
|
||||
);
|
||||
return (
|
||||
<Popover isLazy>
|
||||
<PopoverTrigger>
|
||||
<IconButton
|
||||
aria-label={t('controlLayers.opacity')}
|
||||
size="sm"
|
||||
icon={<PiDropHalfFill size={16} />}
|
||||
variant="ghost"
|
||||
onDoubleClick={stopPropagation}
|
||||
/>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent onDoubleClick={stopPropagation}>
|
||||
<PopoverArrow />
|
||||
<PopoverBody>
|
||||
<Flex direction="column" gap={2}>
|
||||
<FormControl orientation="horizontal">
|
||||
<FormLabel m={0}>{t('controlLayers.opacity')}</FormLabel>
|
||||
<CompositeSlider
|
||||
min={0}
|
||||
max={100}
|
||||
step={1}
|
||||
value={opacity}
|
||||
defaultValue={100}
|
||||
onChange={onChangeOpacity}
|
||||
marks={marks}
|
||||
w={48}
|
||||
/>
|
||||
<CompositeNumberInput
|
||||
min={0}
|
||||
max={100}
|
||||
step={1}
|
||||
value={opacity}
|
||||
defaultValue={100}
|
||||
onChange={onChangeOpacity}
|
||||
w={24}
|
||||
format={formatPct}
|
||||
/>
|
||||
</FormControl>
|
||||
</Flex>
|
||||
</PopoverBody>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
);
|
||||
});
|
||||
|
||||
LayerOpacity.displayName = 'LayerOpacity';
|
@ -40,11 +40,6 @@ const getIndexAndCount = (
|
||||
index: canvasV2.layers.entities.findIndex((entity) => entity.id === id),
|
||||
count: canvasV2.layers.entities.length,
|
||||
};
|
||||
} else if (type === 'control_adapter') {
|
||||
return {
|
||||
index: canvasV2.controlAdapters.entities.findIndex((entity) => entity.id === id),
|
||||
count: canvasV2.controlAdapters.entities.length,
|
||||
};
|
||||
} else if (type === 'regional_guidance') {
|
||||
return {
|
||||
index: canvasV2.regions.entities.findIndex((entity) => entity.id === id),
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { deepClone } from 'common/util/deepClone';
|
||||
import { caAdded, ipaAdded, rgIPAdapterAdded } from 'features/controlLayers/store/canvasV2Slice';
|
||||
import { ipaAdded, rgIPAdapterAdded } from 'features/controlLayers/store/canvasV2Slice';
|
||||
import {
|
||||
IMAGE_FILTERS,
|
||||
initialControlNetV2,
|
||||
@ -37,7 +37,7 @@ export const useAddCALayer = () => {
|
||||
const initialConfig = deepClone(model.type === 'controlnet' ? initialControlNetV2 : initialT2IAdapterV2);
|
||||
const config = { ...initialConfig, model: zModelIdentifierField.parse(model), processorConfig };
|
||||
|
||||
dispatch(caAdded({ config }));
|
||||
// dispatch(caAdded({ config }));
|
||||
}, [dispatch, model, baseModel]);
|
||||
|
||||
return [addCALayer, isDisabled] as const;
|
||||
|
@ -100,7 +100,12 @@ export class CanvasFilter {
|
||||
this.manager.stateApi.rasterizeEntity({
|
||||
entityIdentifier: this.parent.getEntityIdentifier(),
|
||||
imageObject: this.imageState,
|
||||
position: { x: Math.round(rect.x), y: Math.round(rect.y) },
|
||||
rect: {
|
||||
x: Math.round(rect.x),
|
||||
y: Math.round(rect.y),
|
||||
width: this.imageState.image.height,
|
||||
height: this.imageState.image.width,
|
||||
},
|
||||
});
|
||||
this.parent.renderer.showObjects();
|
||||
this.manager.stateApi.$filteringEntity.set(null);
|
||||
|
@ -9,20 +9,27 @@ import {
|
||||
konvaNodeToBlob,
|
||||
konvaNodeToImageData,
|
||||
nanoid,
|
||||
previewBlob,
|
||||
} from 'features/controlLayers/konva/util';
|
||||
import type { Extents, ExtentsResult, GetBboxTask, WorkerLogMessage } from 'features/controlLayers/konva/worker';
|
||||
import type { CanvasV2State, Coordinate, Dimensions, GenerationMode, Rect } from 'features/controlLayers/store/types';
|
||||
import type {
|
||||
CanvasV2State,
|
||||
Coordinate,
|
||||
Dimensions,
|
||||
GenerationMode,
|
||||
ImageCache,
|
||||
Rect,
|
||||
} from 'features/controlLayers/store/types';
|
||||
import { isValidLayerWithoutControlAdapter } from 'features/nodes/util/graph/generation/addLayers';
|
||||
import type Konva from 'konva';
|
||||
import { clamp } from 'lodash-es';
|
||||
import { clamp, isEqual } from 'lodash-es';
|
||||
import { atom } from 'nanostores';
|
||||
import type { Logger } from 'roarr';
|
||||
import { getImageDTO, uploadImage } from 'services/api/endpoints/images';
|
||||
import type { ImageDTO } from 'services/api/types';
|
||||
import { assert } from 'tsafe';
|
||||
|
||||
import { CanvasBackground } from './CanvasBackground';
|
||||
import { CanvasControlAdapter } from './CanvasControlAdapter';
|
||||
import type { CanvasControlAdapter } from './CanvasControlAdapter';
|
||||
import { CanvasLayerAdapter } from './CanvasLayerAdapter';
|
||||
import { CanvasMaskAdapter } from './CanvasMaskAdapter';
|
||||
import { CanvasPreview } from './CanvasPreview';
|
||||
@ -144,40 +151,15 @@ export class CanvasManager {
|
||||
this._worker.postMessage(task, [data.buffer]);
|
||||
}
|
||||
|
||||
async renderControlAdapters() {
|
||||
const { entities } = this.stateApi.getControlAdaptersState();
|
||||
|
||||
for (const canvasControlAdapter of this.controlAdapters.values()) {
|
||||
if (!entities.find((ca) => ca.id === canvasControlAdapter.id)) {
|
||||
canvasControlAdapter.destroy();
|
||||
this.controlAdapters.delete(canvasControlAdapter.id);
|
||||
}
|
||||
}
|
||||
|
||||
for (const entity of entities) {
|
||||
let adapter = this.controlAdapters.get(entity.id);
|
||||
if (!adapter) {
|
||||
adapter = new CanvasControlAdapter(entity, this);
|
||||
this.controlAdapters.set(adapter.id, adapter);
|
||||
this.stage.add(adapter.konva.layer);
|
||||
}
|
||||
await adapter.render(entity);
|
||||
}
|
||||
}
|
||||
|
||||
arrangeEntities() {
|
||||
const { getLayersState, getControlAdaptersState, getRegionsState } = this.stateApi;
|
||||
const { getLayersState, getRegionsState } = this.stateApi;
|
||||
const layers = getLayersState().entities;
|
||||
const controlAdapters = getControlAdaptersState().entities;
|
||||
const regions = getRegionsState().entities;
|
||||
let zIndex = 0;
|
||||
this.background.konva.layer.zIndex(++zIndex);
|
||||
for (const layer of layers) {
|
||||
this.layers.get(layer.id)?.konva.layer.zIndex(++zIndex);
|
||||
}
|
||||
for (const ca of controlAdapters) {
|
||||
this.controlAdapters.get(ca.id)?.konva.layer.zIndex(++zIndex);
|
||||
}
|
||||
for (const rg of regions) {
|
||||
this.regions.get(rg.id)?.konva.layer.zIndex(++zIndex);
|
||||
}
|
||||
@ -358,16 +340,6 @@ export class CanvasManager {
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
this._isFirstRender ||
|
||||
state.controlAdapters.entities !== this._prevState.controlAdapters.entities ||
|
||||
state.tool.selected !== this._prevState.tool.selected ||
|
||||
state.selectedEntityIdentifier?.id !== this._prevState.selectedEntityIdentifier?.id
|
||||
) {
|
||||
this.log.debug('Rendering control adapters');
|
||||
await this.renderControlAdapters();
|
||||
}
|
||||
|
||||
this.stateApi.$toolState.set(state.tool);
|
||||
this.stateApi.$selectedEntityIdentifier.set(state.selectedEntityIdentifier);
|
||||
this.stateApi.$selectedEntity.set(this.stateApi.getSelectedEntity());
|
||||
@ -382,12 +354,7 @@ export class CanvasManager {
|
||||
await this.preview.bbox.render();
|
||||
}
|
||||
|
||||
if (
|
||||
this._isFirstRender ||
|
||||
state.layers !== this._prevState.layers ||
|
||||
state.controlAdapters !== this._prevState.controlAdapters ||
|
||||
state.regions !== this._prevState.regions
|
||||
) {
|
||||
if (this._isFirstRender || state.layers !== this._prevState.layers || state.regions !== this._prevState.regions) {
|
||||
// this.log.debug('Updating entity bboxes');
|
||||
// debouncedUpdateBboxes(stage, canvasV2.layers, canvasV2.controlAdapters, canvasV2.regions, onBboxChanged);
|
||||
}
|
||||
@ -400,7 +367,6 @@ export class CanvasManager {
|
||||
if (
|
||||
this._isFirstRender ||
|
||||
state.layers.entities !== this._prevState.layers.entities ||
|
||||
state.controlAdapters.entities !== this._prevState.controlAdapters.entities ||
|
||||
state.regions.entities !== this._prevState.regions.entities ||
|
||||
state.inpaintMask !== this._prevState.inpaintMask ||
|
||||
state.selectedEntityIdentifier?.id !== this._prevState.selectedEntityIdentifier?.id
|
||||
@ -569,45 +535,43 @@ export class CanvasManager {
|
||||
return konvaNodeToImageData(this.getCompositeLayerStageClone(), rect);
|
||||
};
|
||||
|
||||
getCompositeLayerImageDTO = async (rect?: Rect): Promise<ImageDTO> => {
|
||||
getCompositeRasterizedImageCache = (rect: Rect): ImageCache | null => {
|
||||
const layerState = this.stateApi.getLayersState();
|
||||
const imageCache = layerState.compositeRasterizationCache.find((cache) => isEqual(cache.rect, rect));
|
||||
return imageCache ?? null;
|
||||
};
|
||||
|
||||
getCompositeLayerImageDTO = async (rect: Rect): Promise<ImageDTO> => {
|
||||
let imageDTO: ImageDTO | null = null;
|
||||
const compositeRasterizedImageCache = this.getCompositeRasterizedImageCache(rect);
|
||||
|
||||
if (compositeRasterizedImageCache) {
|
||||
imageDTO = await getImageDTO(compositeRasterizedImageCache.imageName);
|
||||
if (imageDTO) {
|
||||
this.log.trace({ rect, compositeRasterizedImageCache, imageDTO }, 'Using cached composite rasterized image');
|
||||
return imageDTO;
|
||||
}
|
||||
}
|
||||
|
||||
this.log.trace({ rect }, 'Rasterizing composite layer');
|
||||
|
||||
const blob = await this.getCompositeLayerBlob(rect);
|
||||
const imageDTO = await uploadImage(blob, 'composite-layer.png', 'general', true);
|
||||
this.stateApi.setLayerImageCache(imageDTO);
|
||||
|
||||
if (this._isDebugging) {
|
||||
previewBlob(blob, 'Rasterized entity');
|
||||
}
|
||||
|
||||
imageDTO = await uploadImage(blob, 'composite-layer.png', 'general', true);
|
||||
this.stateApi.compositeLayerRasterized({ imageName: imageDTO.image_name, rect });
|
||||
return imageDTO;
|
||||
};
|
||||
|
||||
getInpaintMaskBlob = (rect?: Rect): Promise<Blob> => {
|
||||
return this.inpaintMask.renderer.getBlob({ rect });
|
||||
return this.inpaintMask.renderer.getBlob(rect);
|
||||
};
|
||||
|
||||
getInpaintMaskImageData = (rect?: Rect): ImageData => {
|
||||
return this.inpaintMask.renderer.getImageData({ rect });
|
||||
};
|
||||
|
||||
getInpaintMaskImageDTO = async (rect?: Rect): Promise<ImageDTO> => {
|
||||
const blob = await this.inpaintMask.renderer.getBlob({ rect });
|
||||
const imageDTO = await uploadImage(blob, 'inpaint-mask.png', 'mask', true);
|
||||
this.stateApi.setInpaintMaskImageCache(imageDTO);
|
||||
return imageDTO;
|
||||
};
|
||||
|
||||
getRegionMaskImageDTO = async (id: string, rect?: Rect): Promise<ImageDTO> => {
|
||||
const region = this.stateApi.getEntity({ id, type: 'regional_guidance' });
|
||||
assert(region?.type === 'regional_guidance');
|
||||
if (region.state.imageCache) {
|
||||
const imageDTO = await getImageDTO(region.state.imageCache);
|
||||
if (imageDTO) {
|
||||
return imageDTO;
|
||||
}
|
||||
}
|
||||
return region.adapter.renderer.getImageDTO({
|
||||
rect,
|
||||
category: 'other',
|
||||
is_intermediate: true,
|
||||
onUploaded: (imageDTO) => {
|
||||
this.stateApi.setRegionMaskImageCache(region.state.id, imageDTO);
|
||||
},
|
||||
});
|
||||
return this.inpaintMask.renderer.getImageData(rect);
|
||||
};
|
||||
|
||||
getGenerationMode(): GenerationMode {
|
||||
|
@ -14,14 +14,16 @@ import type {
|
||||
CanvasEraserLineState,
|
||||
CanvasImageState,
|
||||
CanvasRectState,
|
||||
ImageCache,
|
||||
Rect,
|
||||
RgbColor,
|
||||
} from 'features/controlLayers/store/types';
|
||||
import { imageDTOToImageObject } from 'features/controlLayers/store/types';
|
||||
import Konva from 'konva';
|
||||
import { isEqual } from 'lodash-es';
|
||||
import type { Logger } from 'roarr';
|
||||
import { getImageDTO, uploadImage } from 'services/api/endpoints/images';
|
||||
import type { ImageCategory, ImageDTO } from 'services/api/types';
|
||||
import type { ImageDTO } from 'services/api/types';
|
||||
import { assert } from 'tsafe';
|
||||
|
||||
/**
|
||||
@ -62,12 +64,6 @@ export class CanvasObjectRenderer {
|
||||
*/
|
||||
renderers: Map<string, AnyObjectRenderer> = new Map();
|
||||
|
||||
/**
|
||||
* A cache of the rasterized image data URL. If the cache is null, the parent has not been rasterized since its last
|
||||
* change.
|
||||
*/
|
||||
rasterizedImageCache: string | null = null;
|
||||
|
||||
/**
|
||||
* A object containing singleton Konva nodes.
|
||||
*/
|
||||
@ -168,19 +164,6 @@ export class CanvasObjectRenderer {
|
||||
didRender = (await this.renderObject(this.buffer)) || didRender;
|
||||
}
|
||||
|
||||
if (didRender && this.rasterizedImageCache) {
|
||||
const hasOneObject = this.renderers.size === 1;
|
||||
const firstObject = Array.from(this.renderers.values())[0];
|
||||
if (
|
||||
hasOneObject &&
|
||||
firstObject &&
|
||||
firstObject.state.type === 'image' &&
|
||||
firstObject.state.image.image_name !== this.rasterizedImageCache
|
||||
) {
|
||||
this.rasterizedImageCache = null;
|
||||
}
|
||||
}
|
||||
|
||||
return didRender;
|
||||
};
|
||||
|
||||
@ -376,29 +359,37 @@ export class CanvasObjectRenderer {
|
||||
return this.renderers.size > 0 || this.buffer !== null;
|
||||
};
|
||||
|
||||
getRasterizedImageCache = (rect: Rect): ImageCache | null => {
|
||||
const imageCache = this.parent.state.rasterizationCache.find((cache) => isEqual(cache.rect, rect));
|
||||
return imageCache ?? null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Rasterizes the parent entity. If the entity has a rasterization cache, the cached image is returned after
|
||||
* validating that it exists on the server.
|
||||
* Rasterizes the parent entity. If the entity has a rasterization cache for the given rect, the cached image is
|
||||
* returned. Otherwise, the entity is rasterized and the image is uploaded to the server.
|
||||
*
|
||||
* The rasterization cache is reset when the entity's objects change. The buffer object is not considered part of the
|
||||
* entity's objects for this purpose.
|
||||
* The rasterization cache is reset when the entity's state changes. The buffer object is not considered part of the
|
||||
* entity state for this purpose as it is a temporary object.
|
||||
*
|
||||
* @param rect The rect to rasterize. If omitted, the entity's full rect will be used.
|
||||
* @returns A promise that resolves to the rasterized image DTO.
|
||||
*/
|
||||
rasterize = async (): Promise<ImageDTO> => {
|
||||
this.log.debug('Rasterizing entity');
|
||||
|
||||
rasterize = async (rect?: Rect): Promise<ImageDTO> => {
|
||||
rect = rect ?? this.parent.transformer.getRelativeRect();
|
||||
let imageDTO: ImageDTO | null = null;
|
||||
if (this.rasterizedImageCache) {
|
||||
imageDTO = await getImageDTO(this.rasterizedImageCache);
|
||||
const rasterizedImageCache = this.getRasterizedImageCache(rect);
|
||||
|
||||
if (rasterizedImageCache) {
|
||||
imageDTO = await getImageDTO(rasterizedImageCache.imageName);
|
||||
if (imageDTO) {
|
||||
this.log.trace({ rect, rasterizedImageCache, imageDTO }, 'Using cached rasterized image');
|
||||
return imageDTO;
|
||||
}
|
||||
}
|
||||
|
||||
if (imageDTO) {
|
||||
return imageDTO;
|
||||
}
|
||||
this.log.trace({ rect }, 'Rasterizing entity');
|
||||
|
||||
const rect = this.parent.transformer.getRelativeRect();
|
||||
const blob = await this.getBlob({ rect });
|
||||
const blob = await this.getBlob(rect);
|
||||
if (this.manager._isDebugging) {
|
||||
previewBlob(blob, 'Rasterized entity');
|
||||
}
|
||||
@ -408,41 +399,20 @@ export class CanvasObjectRenderer {
|
||||
this.manager.stateApi.rasterizeEntity({
|
||||
entityIdentifier: this.parent.getEntityIdentifier(),
|
||||
imageObject,
|
||||
position: { x: Math.round(rect.x), y: Math.round(rect.y) },
|
||||
rect: { x: Math.round(rect.x), y: Math.round(rect.y), width: imageDTO.width, height: imageDTO.height },
|
||||
});
|
||||
|
||||
this.rasterizedImageCache = imageDTO.image_name;
|
||||
|
||||
return imageDTO;
|
||||
};
|
||||
|
||||
getBlob = ({ rect }: { rect?: Rect }): Promise<Blob> => {
|
||||
getBlob = (rect?: Rect): Promise<Blob> => {
|
||||
return konvaNodeToBlob(this.konva.objectGroup.clone(), rect);
|
||||
};
|
||||
|
||||
getImageData = ({ rect }: { rect?: Rect }): ImageData => {
|
||||
getImageData = (rect?: Rect): ImageData => {
|
||||
return konvaNodeToImageData(this.konva.objectGroup.clone(), rect);
|
||||
};
|
||||
|
||||
getImageDTO = async ({
|
||||
rect,
|
||||
category,
|
||||
is_intermediate,
|
||||
onUploaded,
|
||||
}: {
|
||||
rect?: Rect;
|
||||
category: ImageCategory;
|
||||
is_intermediate: boolean;
|
||||
onUploaded?: (imageDTO: ImageDTO) => void;
|
||||
}): Promise<ImageDTO> => {
|
||||
const blob = await this.getBlob({ rect });
|
||||
const imageDTO = await uploadImage(blob, `${this.id}.png`, category, is_intermediate);
|
||||
if (onUploaded) {
|
||||
onUploaded(imageDTO);
|
||||
}
|
||||
return imageDTO;
|
||||
};
|
||||
|
||||
/**
|
||||
* Destroys this renderer and all of its object renderers.
|
||||
*/
|
||||
|
@ -26,9 +26,7 @@ import {
|
||||
entityReset,
|
||||
entitySelected,
|
||||
eraserWidthChanged,
|
||||
imImageCacheChanged,
|
||||
layerImageCacheChanged,
|
||||
rgImageCacheChanged,
|
||||
layerCompositeRasterized,
|
||||
toolBufferChanged,
|
||||
toolChanged,
|
||||
} from 'features/controlLayers/store/canvasV2Slice';
|
||||
@ -51,7 +49,6 @@ import type {
|
||||
import { RGBA_RED } from 'features/controlLayers/store/types';
|
||||
import type { WritableAtom } from 'nanostores';
|
||||
import { atom } from 'nanostores';
|
||||
import type { ImageDTO } from 'services/api/types';
|
||||
|
||||
type EntityStateAndAdapter =
|
||||
| {
|
||||
@ -118,6 +115,10 @@ export class CanvasStateApi {
|
||||
log.trace(arg, 'Rasterizing entity');
|
||||
this._store.dispatch(entityRasterized(arg));
|
||||
};
|
||||
compositeLayerRasterized = (arg: { imageName: string; rect: Rect }) => {
|
||||
log.trace(arg, 'Composite layer rasterized');
|
||||
this._store.dispatch(layerCompositeRasterized(arg));
|
||||
};
|
||||
setSelectedEntity = (arg: EntityIdentifierPayload) => {
|
||||
log.trace({ arg }, 'Setting selected entity');
|
||||
this._store.dispatch(entitySelected(arg));
|
||||
@ -134,18 +135,6 @@ export class CanvasStateApi {
|
||||
log.trace({ width }, 'Setting eraser width');
|
||||
this._store.dispatch(eraserWidthChanged(width));
|
||||
};
|
||||
setRegionMaskImageCache = (id: string, imageDTO: ImageDTO) => {
|
||||
log.trace({ id, imageDTO }, 'Setting region mask image cache');
|
||||
this._store.dispatch(rgImageCacheChanged({ id, imageDTO }));
|
||||
};
|
||||
setInpaintMaskImageCache = (imageDTO: ImageDTO) => {
|
||||
log.trace({ imageDTO }, 'Setting inpaint mask image cache');
|
||||
this._store.dispatch(imImageCacheChanged({ imageDTO }));
|
||||
};
|
||||
setLayerImageCache = (imageDTO: ImageDTO) => {
|
||||
log.trace({ imageDTO }, 'Setting layer image cache');
|
||||
this._store.dispatch(layerImageCacheChanged({ imageDTO }));
|
||||
};
|
||||
setTool = (tool: Tool) => {
|
||||
log.trace({ tool }, 'Setting tool');
|
||||
this._store.dispatch(toolChanged(tool));
|
||||
@ -171,9 +160,6 @@ export class CanvasStateApi {
|
||||
getLayersState = () => {
|
||||
return this.getState().layers;
|
||||
};
|
||||
getControlAdaptersState = () => {
|
||||
return this.getState().controlAdapters;
|
||||
};
|
||||
getInpaintMaskState = () => {
|
||||
return this.getState().inpaintMask;
|
||||
};
|
||||
@ -202,9 +188,6 @@ export class CanvasStateApi {
|
||||
if (identifier.type === 'layer') {
|
||||
entityState = state.layers.entities.find((i) => i.id === identifier.id) ?? null;
|
||||
entityAdapter = this.manager.layers.get(identifier.id) ?? null;
|
||||
} else if (identifier.type === 'control_adapter') {
|
||||
entityState = state.controlAdapters.entities.find((i) => i.id === identifier.id) ?? null;
|
||||
entityAdapter = this.manager.controlAdapters.get(identifier.id) ?? null;
|
||||
} else if (identifier.type === 'regional_guidance') {
|
||||
entityState = state.regions.entities.find((i) => i.id === identifier.id) ?? null;
|
||||
entityAdapter = this.manager.regions.get(identifier.id) ?? null;
|
||||
|
@ -5,7 +5,6 @@ import { moveOneToEnd, moveOneToStart, moveToEnd, moveToStart } from 'common/uti
|
||||
import { deepClone } from 'common/util/deepClone';
|
||||
import { bboxReducers } from 'features/controlLayers/store/bboxReducers';
|
||||
import { compositingReducers } from 'features/controlLayers/store/compositingReducers';
|
||||
import { controlAdaptersReducers } from 'features/controlLayers/store/controlAdaptersReducers';
|
||||
import { inpaintMaskReducers } from 'features/controlLayers/store/inpaintMaskReducers';
|
||||
import { ipAdaptersReducers } from 'features/controlLayers/store/ipAdaptersReducers';
|
||||
import { layersReducers } from 'features/controlLayers/store/layersReducers';
|
||||
@ -18,13 +17,16 @@ import { toolReducers } from 'features/controlLayers/store/toolReducers';
|
||||
import { getScaledBoundingBoxDimensions } from 'features/controlLayers/util/getScaledBoundingBoxDimensions';
|
||||
import { initialAspectRatioState } from 'features/parameters/components/DocumentSize/constants';
|
||||
import { getOptimalDimension } from 'features/parameters/util/optimalDimension';
|
||||
import { pick } from 'lodash-es';
|
||||
import { isEqual, pick } from 'lodash-es';
|
||||
import { atom } from 'nanostores';
|
||||
import type { InvocationDenoiseProgressEvent } from 'services/events/types';
|
||||
import { assert } from 'tsafe';
|
||||
|
||||
import type {
|
||||
CanvasEntityIdentifier,
|
||||
CanvasInpaintMaskState,
|
||||
CanvasLayerState,
|
||||
CanvasRegionalGuidanceState,
|
||||
CanvasV2State,
|
||||
Coordinate,
|
||||
EntityBrushLineAddedPayload,
|
||||
@ -41,8 +43,7 @@ import { IMAGE_FILTERS, RGBA_RED } from './types';
|
||||
const initialState: CanvasV2State = {
|
||||
_version: 3,
|
||||
selectedEntityIdentifier: null,
|
||||
layers: { entities: [], imageCache: null },
|
||||
controlAdapters: { entities: [] },
|
||||
layers: { entities: [], compositeRasterizationCache: [] },
|
||||
ipAdapters: { entities: [] },
|
||||
regions: { entities: [] },
|
||||
loras: [],
|
||||
@ -50,7 +51,7 @@ const initialState: CanvasV2State = {
|
||||
id: 'inpaint_mask',
|
||||
type: 'inpaint_mask',
|
||||
fill: RGBA_RED,
|
||||
imageCache: null,
|
||||
rasterizationCache: [],
|
||||
isEnabled: true,
|
||||
objects: [],
|
||||
position: {
|
||||
@ -144,8 +145,6 @@ export function selectEntity(state: CanvasV2State, { id, type }: CanvasEntityIde
|
||||
switch (type) {
|
||||
case 'layer':
|
||||
return state.layers.entities.find((layer) => layer.id === id);
|
||||
case 'control_adapter':
|
||||
return state.controlAdapters.entities.find((ca) => ca.id === id);
|
||||
case 'inpaint_mask':
|
||||
return state.inpaintMask;
|
||||
case 'regional_guidance':
|
||||
@ -157,13 +156,37 @@ export function selectEntity(state: CanvasV2State, { id, type }: CanvasEntityIde
|
||||
}
|
||||
}
|
||||
|
||||
const invalidateCompositeRasterizationCache = (entity: CanvasLayerState, state: CanvasV2State) => {
|
||||
if (entity.controlAdapter === null) {
|
||||
state.layers.compositeRasterizationCache = [];
|
||||
}
|
||||
};
|
||||
|
||||
const invalidateRasterizationCaches = (
|
||||
entity: CanvasLayerState | CanvasInpaintMaskState | CanvasRegionalGuidanceState,
|
||||
state: CanvasV2State
|
||||
) => {
|
||||
// TODO(psyche): We can be more efficient and only invalidate caches when the entity's changes intersect with the
|
||||
// cached rect.
|
||||
|
||||
// Reset the entity's rasterization cache
|
||||
entity.rasterizationCache = [];
|
||||
|
||||
// When an individual layer has its cache reset, we must also reset the composite rasterization cache because the
|
||||
// layer's image data will contribute to the composite layer's image data.
|
||||
// If the layer is used as a control layer, it will not contribute to the composite layer, so we do not need to reset
|
||||
// its cache.
|
||||
if (entity.type === 'layer') {
|
||||
invalidateCompositeRasterizationCache(entity, state);
|
||||
}
|
||||
};
|
||||
|
||||
export const canvasV2Slice = createSlice({
|
||||
name: 'canvasV2',
|
||||
initialState,
|
||||
reducers: {
|
||||
...layersReducers,
|
||||
...ipAdaptersReducers,
|
||||
...controlAdaptersReducers,
|
||||
...regionsReducers,
|
||||
...lorasReducers,
|
||||
...paramsReducers,
|
||||
@ -182,16 +205,11 @@ export const canvasV2Slice = createSlice({
|
||||
const entity = selectEntity(state, entityIdentifier);
|
||||
if (!entity) {
|
||||
return;
|
||||
} else if (entity.type === 'layer') {
|
||||
} else if (entity.type === 'layer' || entity.type === 'inpaint_mask' || entity.type === 'regional_guidance') {
|
||||
entity.isEnabled = true;
|
||||
entity.objects = [];
|
||||
entity.position = { x: 0, y: 0 };
|
||||
state.layers.imageCache = null;
|
||||
} else if (entity.type === 'inpaint_mask' || entity.type === 'regional_guidance') {
|
||||
entity.isEnabled = true;
|
||||
entity.objects = [];
|
||||
entity.position = { x: 0, y: 0 };
|
||||
entity.imageCache = null;
|
||||
invalidateRasterizationCaches(entity, state);
|
||||
} else {
|
||||
assert(false, 'Not implemented');
|
||||
}
|
||||
@ -209,32 +227,28 @@ export const canvasV2Slice = createSlice({
|
||||
const entity = selectEntity(state, entityIdentifier);
|
||||
if (!entity) {
|
||||
return;
|
||||
} else if (entity.type === 'layer') {
|
||||
}
|
||||
|
||||
if (entity.type === 'layer' || entity.type === 'inpaint_mask' || entity.type === 'regional_guidance') {
|
||||
entity.position = position;
|
||||
state.layers.imageCache = null;
|
||||
} else if (entity.type === 'inpaint_mask' || entity.type === 'regional_guidance') {
|
||||
entity.position = position;
|
||||
entity.imageCache = null;
|
||||
} else {
|
||||
assert(false, 'Not implemented');
|
||||
// When an entity is moved, we need to invalidate the rasterization caches.
|
||||
invalidateRasterizationCaches(entity, state);
|
||||
}
|
||||
},
|
||||
entityRasterized: (state, action: PayloadAction<EntityRasterizedPayload>) => {
|
||||
const { entityIdentifier, imageObject, position } = action.payload;
|
||||
const { entityIdentifier, imageObject, rect } = action.payload;
|
||||
const entity = selectEntity(state, entityIdentifier);
|
||||
if (!entity) {
|
||||
return;
|
||||
} else if (entity.type === 'layer') {
|
||||
}
|
||||
|
||||
if (entity.type === 'layer' || entity.type === 'inpaint_mask' || entity.type === 'regional_guidance') {
|
||||
entity.objects = [imageObject];
|
||||
entity.position = position;
|
||||
entity.imageCache = imageObject.image.image_name;
|
||||
state.layers.imageCache = null;
|
||||
} else if (entity.type === 'inpaint_mask' || entity.type === 'regional_guidance') {
|
||||
entity.objects = [imageObject];
|
||||
entity.position = position;
|
||||
entity.imageCache = imageObject.image.image_name;
|
||||
} else {
|
||||
assert(false, 'Not implemented');
|
||||
entity.position = { x: rect.x, y: rect.y };
|
||||
// Remove the cache for the given rect. This should never happen, because we should never rasterize the same
|
||||
// rect twice. Just in case, we remove the old cache.
|
||||
entity.rasterizationCache = entity.rasterizationCache.filter((cache) => !isEqual(cache.rect, rect));
|
||||
entity.rasterizationCache.push({ imageName: imageObject.image.image_name, rect });
|
||||
}
|
||||
},
|
||||
entityBrushLineAdded: (state, action: PayloadAction<EntityBrushLineAddedPayload>) => {
|
||||
@ -242,14 +256,12 @@ export const canvasV2Slice = createSlice({
|
||||
const entity = selectEntity(state, entityIdentifier);
|
||||
if (!entity) {
|
||||
return;
|
||||
} else if (entity.type === 'layer') {
|
||||
}
|
||||
|
||||
if (entity.type === 'layer' || entity.type === 'inpaint_mask' || entity.type === 'regional_guidance') {
|
||||
entity.objects.push(brushLine);
|
||||
state.layers.imageCache = null;
|
||||
} else if (entity.type === 'inpaint_mask' || entity.type === 'regional_guidance') {
|
||||
entity.objects.push(brushLine);
|
||||
entity.imageCache = null;
|
||||
} else {
|
||||
assert(false, 'Not implemented');
|
||||
// When adding a brush line, we need to invalidate the rasterization caches.
|
||||
invalidateRasterizationCaches(entity, state);
|
||||
}
|
||||
},
|
||||
entityEraserLineAdded: (state, action: PayloadAction<EntityEraserLineAddedPayload>) => {
|
||||
@ -257,12 +269,10 @@ export const canvasV2Slice = createSlice({
|
||||
const entity = selectEntity(state, entityIdentifier);
|
||||
if (!entity) {
|
||||
return;
|
||||
} else if (entity.type === 'layer') {
|
||||
} else if (entity.type === 'layer' || entity.type === 'inpaint_mask' || entity.type === 'regional_guidance') {
|
||||
entity.objects.push(eraserLine);
|
||||
state.layers.imageCache = null;
|
||||
} else if (entity.type === 'inpaint_mask' || entity.type === 'regional_guidance') {
|
||||
entity.objects.push(eraserLine);
|
||||
entity.imageCache = null;
|
||||
// When adding an eraser line, we need to invalidate the rasterization caches.
|
||||
invalidateRasterizationCaches(entity, state);
|
||||
} else {
|
||||
assert(false, 'Not implemented');
|
||||
}
|
||||
@ -274,19 +284,21 @@ export const canvasV2Slice = createSlice({
|
||||
return;
|
||||
} else if (entity.type === 'layer') {
|
||||
entity.objects.push(rect);
|
||||
state.layers.imageCache = null;
|
||||
} else if (entity.type === 'inpaint_mask' || entity.type === 'regional_guidance') {
|
||||
entity.objects.push(rect);
|
||||
entity.imageCache = null;
|
||||
// When adding an eraser line, we need to invalidate the rasterization caches.
|
||||
invalidateRasterizationCaches(entity, state);
|
||||
} else {
|
||||
assert(false, 'Not implemented');
|
||||
}
|
||||
},
|
||||
entityDeleted: (state, action: PayloadAction<EntityIdentifierPayload>) => {
|
||||
const { entityIdentifier } = action.payload;
|
||||
const entity = selectEntity(state, entityIdentifier);
|
||||
if (entity?.type === 'layer') {
|
||||
// When a layer is deleted, we may need to invalidate the composite rasterization cache.
|
||||
invalidateCompositeRasterizationCache(entity, state);
|
||||
}
|
||||
if (entityIdentifier.type === 'layer') {
|
||||
state.layers.entities = state.layers.entities.filter((layer) => layer.id !== entityIdentifier.id);
|
||||
state.layers.imageCache = null;
|
||||
} else if (entityIdentifier.type === 'regional_guidance') {
|
||||
state.regions.entities = state.regions.entities.filter((rg) => rg.id !== entityIdentifier.id);
|
||||
} else {
|
||||
@ -301,11 +313,10 @@ export const canvasV2Slice = createSlice({
|
||||
}
|
||||
if (entity.type === 'layer') {
|
||||
moveOneToEnd(state.layers.entities, entity);
|
||||
state.layers.imageCache = null;
|
||||
// When arranging an entity, we may need to invalidate the composite rasterization cache.
|
||||
invalidateCompositeRasterizationCache(entity, state);
|
||||
} else if (entity.type === 'regional_guidance') {
|
||||
moveOneToEnd(state.regions.entities, entity);
|
||||
} else if (entity.type === 'control_adapter') {
|
||||
moveOneToEnd(state.controlAdapters.entities, entity);
|
||||
}
|
||||
},
|
||||
entityArrangedToFront: (state, action: PayloadAction<EntityIdentifierPayload>) => {
|
||||
@ -316,11 +327,10 @@ export const canvasV2Slice = createSlice({
|
||||
}
|
||||
if (entity.type === 'layer') {
|
||||
moveToEnd(state.layers.entities, entity);
|
||||
state.layers.imageCache = null;
|
||||
// When arranging an entity, we may need to invalidate the composite rasterization cache.
|
||||
invalidateCompositeRasterizationCache(entity, state);
|
||||
} else if (entity.type === 'regional_guidance') {
|
||||
moveToEnd(state.regions.entities, entity);
|
||||
} else if (entity.type === 'control_adapter') {
|
||||
moveToEnd(state.controlAdapters.entities, entity);
|
||||
}
|
||||
},
|
||||
entityArrangedBackwardOne: (state, action: PayloadAction<EntityIdentifierPayload>) => {
|
||||
@ -331,11 +341,10 @@ export const canvasV2Slice = createSlice({
|
||||
}
|
||||
if (entity.type === 'layer') {
|
||||
moveOneToStart(state.layers.entities, entity);
|
||||
state.layers.imageCache = null;
|
||||
// When arranging an entity, we may need to invalidate the composite rasterization cache.
|
||||
invalidateCompositeRasterizationCache(entity, state);
|
||||
} else if (entity.type === 'regional_guidance') {
|
||||
moveOneToStart(state.regions.entities, entity);
|
||||
} else if (entity.type === 'control_adapter') {
|
||||
moveOneToStart(state.controlAdapters.entities, entity);
|
||||
}
|
||||
},
|
||||
entityArrangedToBack: (state, action: PayloadAction<EntityIdentifierPayload>) => {
|
||||
@ -346,19 +355,17 @@ export const canvasV2Slice = createSlice({
|
||||
}
|
||||
if (entity.type === 'layer') {
|
||||
moveToStart(state.layers.entities, entity);
|
||||
state.layers.imageCache = null;
|
||||
// When arranging an entity, we may need to invalidate the composite rasterization cache.
|
||||
invalidateCompositeRasterizationCache(entity, state);
|
||||
} else if (entity.type === 'regional_guidance') {
|
||||
moveToStart(state.regions.entities, entity);
|
||||
} else if (entity.type === 'control_adapter') {
|
||||
moveToStart(state.controlAdapters.entities, entity);
|
||||
}
|
||||
},
|
||||
allEntitiesDeleted: (state) => {
|
||||
state.regions.entities = [];
|
||||
state.layers.entities = [];
|
||||
state.layers.imageCache = null;
|
||||
state.layers.compositeRasterizationCache = [];
|
||||
state.ipAdapters.entities = [];
|
||||
state.controlAdapters.entities = [];
|
||||
},
|
||||
filterSelected: (state, action: PayloadAction<{ type: FilterConfig['type'] }>) => {
|
||||
state.filter.config = IMAGE_FILTERS[action.payload.type].buildDefaults();
|
||||
@ -366,6 +373,23 @@ export const canvasV2Slice = createSlice({
|
||||
filterConfigChanged: (state, action: PayloadAction<{ config: FilterConfig }>) => {
|
||||
state.filter.config = action.payload.config;
|
||||
},
|
||||
rasterizationCachesInvalidated: (state) => {
|
||||
// Invalidate the rasterization caches for all entities.
|
||||
|
||||
// Layers & composite layer
|
||||
state.layers.compositeRasterizationCache = [];
|
||||
for (const layer of state.layers.entities) {
|
||||
layer.rasterizationCache = [];
|
||||
}
|
||||
|
||||
// Regions
|
||||
for (const region of state.regions.entities) {
|
||||
region.rasterizationCache = [];
|
||||
}
|
||||
|
||||
// Inpaint mask
|
||||
state.inpaintMask.rasterizationCache = [];
|
||||
},
|
||||
canvasReset: (state) => {
|
||||
state.bbox = deepClone(initialState.bbox);
|
||||
const optimalDimension = getOptimalDimension(state.params.model);
|
||||
@ -374,7 +398,6 @@ export const canvasV2Slice = createSlice({
|
||||
const size = pick(state.bbox.rect, 'width', 'height');
|
||||
state.bbox.scaledSize = getScaledBoundingBoxDimensions(size, optimalDimension);
|
||||
|
||||
state.controlAdapters = deepClone(initialState.controlAdapters);
|
||||
state.ipAdapters = deepClone(initialState.ipAdapters);
|
||||
state.layers = deepClone(initialState.layers);
|
||||
state.regions = deepClone(initialState.regions);
|
||||
@ -397,6 +420,7 @@ export const {
|
||||
allEntitiesDeleted,
|
||||
clipToBboxChanged,
|
||||
canvasReset,
|
||||
rasterizationCachesInvalidated,
|
||||
// All entities
|
||||
entitySelected,
|
||||
entityReset,
|
||||
@ -424,14 +448,13 @@ export const {
|
||||
// layers
|
||||
layerAdded,
|
||||
layerRecalled,
|
||||
layerOpacityChanged,
|
||||
layerAllDeleted,
|
||||
layerImageCacheChanged,
|
||||
layerUsedAsControlChanged,
|
||||
layerControlAdapterModelChanged,
|
||||
layerControlAdapterControlModeChanged,
|
||||
layerControlAdapterWeightChanged,
|
||||
layerControlAdapterBeginEndStepPctChanged,
|
||||
layerCompositeRasterized,
|
||||
// IP Adapters
|
||||
ipaAdded,
|
||||
ipaRecalled,
|
||||
@ -444,20 +467,6 @@ export const {
|
||||
ipaCLIPVisionModelChanged,
|
||||
ipaWeightChanged,
|
||||
ipaBeginEndStepPctChanged,
|
||||
// Control Adapters
|
||||
caAdded,
|
||||
caAllDeleted,
|
||||
caOpacityChanged,
|
||||
caRecalled,
|
||||
caImageChanged,
|
||||
caProcessedImageChanged,
|
||||
caModelChanged,
|
||||
caControlModeChanged,
|
||||
caProcessorConfigChanged,
|
||||
caFilterChanged,
|
||||
caProcessorPendingBatchIdChanged,
|
||||
caWeightChanged,
|
||||
caBeginEndStepPctChanged,
|
||||
// Regions
|
||||
rgAdded,
|
||||
rgRecalled,
|
||||
@ -465,7 +474,6 @@ export const {
|
||||
rgPositivePromptChanged,
|
||||
rgNegativePromptChanged,
|
||||
rgFillChanged,
|
||||
rgImageCacheChanged,
|
||||
rgAutoNegativeChanged,
|
||||
rgIPAdapterAdded,
|
||||
rgIPAdapterDeleted,
|
||||
@ -522,7 +530,6 @@ export const {
|
||||
// Inpaint mask
|
||||
imRecalled,
|
||||
imFillChanged,
|
||||
imImageCacheChanged,
|
||||
// Staging
|
||||
sessionStartedStaging,
|
||||
sessionImageStaged,
|
||||
|
@ -1,197 +0,0 @@
|
||||
import type { PayloadAction, SliceCaseReducers } from '@reduxjs/toolkit';
|
||||
import { zModelIdentifierField } from 'features/nodes/types/common';
|
||||
import { isEqual } from 'lodash-es';
|
||||
import type { ControlNetModelConfig, ImageDTO, T2IAdapterModelConfig } from 'services/api/types';
|
||||
import { assert } from 'tsafe';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import type {
|
||||
CanvasControlAdapterState,
|
||||
CanvasControlNetState,
|
||||
CanvasT2IAdapterState,
|
||||
CanvasV2State,
|
||||
ControlModeV2,
|
||||
ControlNetConfig,
|
||||
Filter,
|
||||
FilterConfig,
|
||||
T2IAdapterConfig,
|
||||
} from './types';
|
||||
import { buildControlAdapterProcessorV2, imageDTOToImageObject } from './types';
|
||||
|
||||
export const selectCA = (state: CanvasV2State, id: string) => state.controlAdapters.entities.find((ca) => ca.id === id);
|
||||
export const selectCAOrThrow = (state: CanvasV2State, id: string) => {
|
||||
const ca = selectCA(state, id);
|
||||
assert(ca, `Control Adapter with id ${id} not found`);
|
||||
return ca;
|
||||
};
|
||||
|
||||
export const controlAdaptersReducers = {
|
||||
caAdded: {
|
||||
reducer: (state, action: PayloadAction<{ id: string; config: ControlNetConfig | T2IAdapterConfig }>) => {
|
||||
const { id, config } = action.payload;
|
||||
state.controlAdapters.entities.push({
|
||||
id,
|
||||
type: 'control_adapter',
|
||||
position: { x: 0, y: 0 },
|
||||
bbox: null,
|
||||
bboxNeedsUpdate: false,
|
||||
isEnabled: true,
|
||||
opacity: 1,
|
||||
filters: ['LightnessToAlphaFilter'],
|
||||
processorPendingBatchId: null,
|
||||
...config,
|
||||
});
|
||||
state.selectedEntityIdentifier = { type: 'control_adapter', id };
|
||||
},
|
||||
prepare: (payload: { config: ControlNetConfig | T2IAdapterConfig }) => ({
|
||||
payload: { id: uuidv4(), ...payload },
|
||||
}),
|
||||
},
|
||||
caRecalled: (state, action: PayloadAction<{ data: CanvasControlAdapterState }>) => {
|
||||
const { data } = action.payload;
|
||||
state.controlAdapters.entities.push(data);
|
||||
state.selectedEntityIdentifier = { type: 'control_adapter', id: data.id };
|
||||
},
|
||||
caAllDeleted: (state) => {
|
||||
state.controlAdapters.entities = [];
|
||||
},
|
||||
caOpacityChanged: (state, action: PayloadAction<{ id: string; opacity: number }>) => {
|
||||
const { id, opacity } = action.payload;
|
||||
const ca = selectCA(state, id);
|
||||
if (!ca) {
|
||||
return;
|
||||
}
|
||||
ca.opacity = opacity;
|
||||
},
|
||||
caImageChanged: {
|
||||
reducer: (state, action: PayloadAction<{ id: string; imageDTO: ImageDTO | null; objectId: string }>) => {
|
||||
const { id, imageDTO, objectId } = action.payload;
|
||||
const ca = selectCA(state, id);
|
||||
if (!ca) {
|
||||
return;
|
||||
}
|
||||
ca.bbox = null;
|
||||
ca.bboxNeedsUpdate = true;
|
||||
ca.isEnabled = true;
|
||||
if (imageDTO) {
|
||||
const newImageObject = imageDTOToImageObject(imageDTO, { filters: ca.filters });
|
||||
if (isEqual(newImageObject, ca.imageObject)) {
|
||||
return;
|
||||
}
|
||||
ca.imageObject = newImageObject;
|
||||
ca.processedImageObject = null;
|
||||
} else {
|
||||
ca.imageObject = null;
|
||||
ca.processedImageObject = null;
|
||||
}
|
||||
},
|
||||
prepare: (payload: { id: string; imageDTO: ImageDTO | null }) => ({ payload: { ...payload, objectId: uuidv4() } }),
|
||||
},
|
||||
caProcessedImageChanged: {
|
||||
reducer: (state, action: PayloadAction<{ id: string; imageDTO: ImageDTO | null; objectId: string }>) => {
|
||||
const { id, imageDTO, objectId } = action.payload;
|
||||
const ca = selectCA(state, id);
|
||||
if (!ca) {
|
||||
return;
|
||||
}
|
||||
ca.bbox = null;
|
||||
ca.bboxNeedsUpdate = true;
|
||||
ca.isEnabled = true;
|
||||
ca.processedImageObject = imageDTO ? imageDTOToImageObject(imageDTO, { filters: ca.filters }) : null;
|
||||
},
|
||||
prepare: (payload: { id: string; imageDTO: ImageDTO | null }) => ({ payload: { ...payload, objectId: uuidv4() } }),
|
||||
},
|
||||
caModelChanged: (
|
||||
state,
|
||||
action: PayloadAction<{
|
||||
id: string;
|
||||
modelConfig: ControlNetModelConfig | T2IAdapterModelConfig | null;
|
||||
}>
|
||||
) => {
|
||||
const { id, modelConfig } = action.payload;
|
||||
const ca = selectCA(state, id);
|
||||
if (!ca) {
|
||||
return;
|
||||
}
|
||||
if (!modelConfig) {
|
||||
ca.model = null;
|
||||
return;
|
||||
}
|
||||
ca.model = zModelIdentifierField.parse(modelConfig);
|
||||
|
||||
const candidateProcessorConfig = buildControlAdapterProcessorV2(modelConfig);
|
||||
if (candidateProcessorConfig?.type !== ca.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.
|
||||
ca.processedImageObject = null;
|
||||
ca.processorConfig = candidateProcessorConfig;
|
||||
}
|
||||
|
||||
// We may need to convert the CA to match the model
|
||||
if (ca.adapterType === 't2i_adapter' && ca.model.type === 'controlnet') {
|
||||
const convertedCA: CanvasControlNetState = { ...ca, adapterType: 'controlnet', controlMode: 'balanced' };
|
||||
state.controlAdapters.entities.splice(state.controlAdapters.entities.indexOf(ca), 1, convertedCA);
|
||||
} else if (ca.adapterType === 'controlnet' && ca.model.type === 't2i_adapter') {
|
||||
const { controlMode: _, ...rest } = ca;
|
||||
const convertedCA: CanvasT2IAdapterState = { ...rest, adapterType: 't2i_adapter' };
|
||||
state.controlAdapters.entities.splice(state.controlAdapters.entities.indexOf(ca), 1, convertedCA);
|
||||
}
|
||||
},
|
||||
caControlModeChanged: (state, action: PayloadAction<{ id: string; controlMode: ControlModeV2 }>) => {
|
||||
const { id, controlMode } = action.payload;
|
||||
const ca = selectCA(state, id);
|
||||
if (!ca || ca.adapterType !== 'controlnet') {
|
||||
return;
|
||||
}
|
||||
ca.controlMode = controlMode;
|
||||
},
|
||||
caProcessorConfigChanged: (state, action: PayloadAction<{ id: string; processorConfig: FilterConfig | null }>) => {
|
||||
const { id, processorConfig } = action.payload;
|
||||
const ca = selectCA(state, id);
|
||||
if (!ca) {
|
||||
return;
|
||||
}
|
||||
ca.processorConfig = processorConfig;
|
||||
if (!processorConfig) {
|
||||
ca.processedImageObject = null;
|
||||
}
|
||||
},
|
||||
caFilterChanged: (state, action: PayloadAction<{ id: string; filters: Filter[] }>) => {
|
||||
const { id, filters } = action.payload;
|
||||
const ca = selectCA(state, id);
|
||||
if (!ca) {
|
||||
return;
|
||||
}
|
||||
ca.filters = filters;
|
||||
if (ca.imageObject) {
|
||||
ca.imageObject.filters = filters;
|
||||
}
|
||||
if (ca.processedImageObject) {
|
||||
ca.processedImageObject.filters = filters;
|
||||
}
|
||||
},
|
||||
caProcessorPendingBatchIdChanged: (state, action: PayloadAction<{ id: string; batchId: string | null }>) => {
|
||||
const { id, batchId } = action.payload;
|
||||
const ca = selectCA(state, id);
|
||||
if (!ca) {
|
||||
return;
|
||||
}
|
||||
ca.processorPendingBatchId = batchId;
|
||||
},
|
||||
caWeightChanged: (state, action: PayloadAction<{ id: string; weight: number }>) => {
|
||||
const { id, weight } = action.payload;
|
||||
const ca = selectCA(state, id);
|
||||
if (!ca) {
|
||||
return;
|
||||
}
|
||||
ca.weight = weight;
|
||||
},
|
||||
caBeginEndStepPctChanged: (state, action: PayloadAction<{ id: string; beginEndStepPct: [number, number] }>) => {
|
||||
const { id, beginEndStepPct } = action.payload;
|
||||
const ca = selectCA(state, id);
|
||||
if (!ca) {
|
||||
return;
|
||||
}
|
||||
ca.beginEndStepPct = beginEndStepPct;
|
||||
},
|
||||
} satisfies SliceCaseReducers<CanvasV2State>;
|
@ -1,7 +1,5 @@
|
||||
import type { PayloadAction, SliceCaseReducers } from '@reduxjs/toolkit';
|
||||
import type { CanvasInpaintMaskState, CanvasV2State } from 'features/controlLayers/store/types';
|
||||
import { imageDTOToImageWithDims } from 'features/controlLayers/store/types';
|
||||
import type { ImageDTO } from 'services/api/types';
|
||||
|
||||
import type { RgbColor } from './types';
|
||||
|
||||
@ -15,8 +13,4 @@ export const inpaintMaskReducers = {
|
||||
const { fill } = action.payload;
|
||||
state.inpaintMask.fill = fill;
|
||||
},
|
||||
imImageCacheChanged: (state, action: PayloadAction<{ imageDTO: ImageDTO | null }>) => {
|
||||
const { imageDTO } = action.payload;
|
||||
state.inpaintMask.imageCache = imageDTO ? imageDTOToImageWithDims(imageDTO) : null;
|
||||
},
|
||||
} satisfies SliceCaseReducers<CanvasV2State>;
|
||||
|
@ -1,12 +1,11 @@
|
||||
import type { PayloadAction, SliceCaseReducers } from '@reduxjs/toolkit';
|
||||
import { getPrefixedId } from 'features/controlLayers/konva/util';
|
||||
import { zModelIdentifierField } from 'features/nodes/types/common';
|
||||
import { merge } from 'lodash-es';
|
||||
import type { ControlNetModelConfig, ImageDTO, T2IAdapterModelConfig } from 'services/api/types';
|
||||
import { isEqual, merge } from 'lodash-es';
|
||||
import type { ControlNetModelConfig, T2IAdapterModelConfig } from 'services/api/types';
|
||||
import { assert } from 'tsafe';
|
||||
|
||||
import type { CanvasLayerState, CanvasV2State, ControlModeV2, ControlNetConfig, T2IAdapterConfig } from './types';
|
||||
import { imageDTOToImageWithDims } from './types';
|
||||
import type { CanvasLayerState, CanvasV2State, ControlModeV2, ControlNetConfig, Rect, T2IAdapterConfig } from './types';
|
||||
|
||||
export const selectLayer = (state: CanvasV2State, id: string) => state.layers.entities.find((layer) => layer.id === id);
|
||||
export const selectLayerOrThrow = (state: CanvasV2State, id: string) => {
|
||||
@ -29,7 +28,7 @@ export const layersReducers = {
|
||||
objects: [],
|
||||
opacity: 1,
|
||||
position: { x: 0, y: 0 },
|
||||
imageCache: null,
|
||||
rasterizationCache: [],
|
||||
controlAdapter: null,
|
||||
};
|
||||
merge(layer, overrides);
|
||||
@ -37,7 +36,11 @@ export const layersReducers = {
|
||||
if (isSelected) {
|
||||
state.selectedEntityIdentifier = { type: 'layer', id };
|
||||
}
|
||||
state.layers.imageCache = null;
|
||||
|
||||
if (layer.objects.length > 0) {
|
||||
// This new layer will change the composite layer's image data. Invalidate the cache.
|
||||
state.layers.compositeRasterizationCache = [];
|
||||
}
|
||||
},
|
||||
prepare: (payload: { overrides?: Partial<CanvasLayerState>; isSelected?: boolean }) => ({
|
||||
payload: { ...payload, id: getPrefixedId('layer') },
|
||||
@ -47,24 +50,20 @@ export const layersReducers = {
|
||||
const { data } = action.payload;
|
||||
state.layers.entities.push(data);
|
||||
state.selectedEntityIdentifier = { type: 'layer', id: data.id };
|
||||
state.layers.imageCache = null;
|
||||
if (data.objects.length > 0) {
|
||||
// This new layer will change the composite layer's image data. Invalidate the cache.
|
||||
state.layers.compositeRasterizationCache = [];
|
||||
}
|
||||
},
|
||||
layerAllDeleted: (state) => {
|
||||
state.layers.entities = [];
|
||||
state.layers.imageCache = null;
|
||||
state.layers.compositeRasterizationCache = [];
|
||||
},
|
||||
layerOpacityChanged: (state, action: PayloadAction<{ id: string; opacity: number }>) => {
|
||||
const { id, opacity } = action.payload;
|
||||
const layer = selectLayer(state, id);
|
||||
if (!layer) {
|
||||
return;
|
||||
}
|
||||
layer.opacity = opacity;
|
||||
state.layers.imageCache = null;
|
||||
},
|
||||
layerImageCacheChanged: (state, action: PayloadAction<{ imageDTO: ImageDTO | null }>) => {
|
||||
const { imageDTO } = action.payload;
|
||||
state.layers.imageCache = imageDTO ? imageDTOToImageWithDims(imageDTO) : null;
|
||||
layerCompositeRasterized: (state, action: PayloadAction<{ imageName: string; rect: Rect }>) => {
|
||||
state.layers.compositeRasterizationCache = state.layers.compositeRasterizationCache.filter(
|
||||
(cache) => !isEqual(cache.rect, action.payload.rect)
|
||||
);
|
||||
state.layers.compositeRasterizationCache.push(action.payload);
|
||||
},
|
||||
layerUsedAsControlChanged: (
|
||||
state,
|
||||
@ -76,6 +75,8 @@ export const layersReducers = {
|
||||
return;
|
||||
}
|
||||
layer.controlAdapter = controlAdapter;
|
||||
// The composite layer's image data will change when the layer is used as control (or not). Invalidate the cache.
|
||||
state.layers.compositeRasterizationCache = [];
|
||||
},
|
||||
layerControlAdapterModelChanged: (
|
||||
state,
|
||||
|
@ -54,7 +54,7 @@ export const regionsReducers = {
|
||||
positivePrompt: '',
|
||||
negativePrompt: null,
|
||||
ipAdapters: [],
|
||||
imageCache: null,
|
||||
rasterizationCache: [],
|
||||
};
|
||||
state.regions.entities.push(rg);
|
||||
state.selectedEntityIdentifier = { type: 'regional_guidance', id };
|
||||
@ -93,14 +93,6 @@ export const regionsReducers = {
|
||||
}
|
||||
rg.fill = fill;
|
||||
},
|
||||
rgImageCacheChanged: (state, action: PayloadAction<{ id: string; imageDTO: ImageDTO }>) => {
|
||||
const { id, imageDTO } = action.payload;
|
||||
const rg = selectRG(state, id);
|
||||
if (!rg) {
|
||||
return;
|
||||
}
|
||||
rg.imageCache = imageDTO.image_name;
|
||||
},
|
||||
rgAutoNegativeChanged: (state, action: PayloadAction<{ id: string; autoNegative: ParameterAutoNegative }>) => {
|
||||
const { id, autoNegative } = action.payload;
|
||||
const rg = selectRG(state, id);
|
||||
|
@ -5,7 +5,7 @@ import { getOptimalDimension } from 'features/parameters/util/optimalDimension';
|
||||
export const selectEntityCount = createSelector(selectCanvasV2Slice, (canvasV2) => {
|
||||
return (
|
||||
canvasV2.regions.entities.length +
|
||||
canvasV2.controlAdapters.entities.length +
|
||||
// canvasV2.controlAdapters.entities.length +
|
||||
canvasV2.ipAdapters.entities.length +
|
||||
canvasV2.layers.entities.length
|
||||
);
|
||||
|
@ -639,6 +639,12 @@ const zMaskObject = z
|
||||
})
|
||||
.pipe(z.discriminatedUnion('type', [zCanvasBrushLineState, zCanvasEraserLineState, zCanvasRectState]));
|
||||
|
||||
const zImageCache = z.object({
|
||||
imageName: z.string(),
|
||||
rect: zRect,
|
||||
});
|
||||
export type ImageCache = z.infer<typeof zImageCache>;
|
||||
|
||||
export const zCanvasRegionalGuidanceState = z.object({
|
||||
id: zId,
|
||||
type: z.literal('regional_guidance'),
|
||||
@ -650,7 +656,7 @@ export const zCanvasRegionalGuidanceState = z.object({
|
||||
negativePrompt: zParameterNegativePrompt.nullable(),
|
||||
ipAdapters: z.array(zCanvasIPAdapterState),
|
||||
autoNegative: zAutoNegative,
|
||||
imageCache: z.string().min(1).nullable(),
|
||||
rasterizationCache: z.array(zImageCache),
|
||||
});
|
||||
export type CanvasRegionalGuidanceState = z.infer<typeof zCanvasRegionalGuidanceState>;
|
||||
|
||||
@ -670,7 +676,7 @@ const zCanvasInpaintMaskState = z.object({
|
||||
position: zCoordinate,
|
||||
fill: zRgbColor,
|
||||
objects: z.array(zCanvasObjectState),
|
||||
imageCache: z.string().min(1).nullable(),
|
||||
rasterizationCache: z.array(zImageCache),
|
||||
});
|
||||
export type CanvasInpaintMaskState = z.infer<typeof zCanvasInpaintMaskState>;
|
||||
|
||||
@ -729,7 +735,7 @@ export const zCanvasLayerState = z.object({
|
||||
position: zCoordinate,
|
||||
opacity: zOpacity,
|
||||
objects: z.array(zCanvasObjectState),
|
||||
imageCache: z.string().min(1).nullable(),
|
||||
rasterizationCache: z.array(zImageCache),
|
||||
controlAdapter: z.discriminatedUnion('type', [zControlNetConfig, zT2IAdapterConfig]).nullable(),
|
||||
});
|
||||
export type CanvasLayerState = z.infer<typeof zCanvasLayerState>;
|
||||
@ -826,11 +832,7 @@ export type CanvasV2State = {
|
||||
_version: 3;
|
||||
selectedEntityIdentifier: CanvasEntityIdentifier | null;
|
||||
inpaintMask: CanvasInpaintMaskState;
|
||||
layers: {
|
||||
imageCache: ImageWithDims | null;
|
||||
entities: CanvasLayerState[];
|
||||
};
|
||||
controlAdapters: { entities: CanvasControlAdapterState[] };
|
||||
layers: { entities: CanvasLayerState[]; compositeRasterizationCache: ImageCache[] };
|
||||
ipAdapters: { entities: CanvasIPAdapterState[] };
|
||||
regions: { entities: CanvasRegionalGuidanceState[] };
|
||||
loras: LoRA[];
|
||||
@ -938,7 +940,7 @@ export type EntityRectAddedPayload = { entityIdentifier: CanvasEntityIdentifier;
|
||||
export type EntityRasterizedPayload = {
|
||||
entityIdentifier: CanvasEntityIdentifier;
|
||||
imageObject: CanvasImageState;
|
||||
position: Coordinate;
|
||||
rect: Rect;
|
||||
};
|
||||
export type ImageObjectAddedArg = { id: string; imageDTO: ImageDTO; position?: Coordinate };
|
||||
|
||||
|
@ -13,7 +13,7 @@ import {
|
||||
import {
|
||||
bboxHeightChanged,
|
||||
bboxWidthChanged,
|
||||
caRecalled,
|
||||
// caRecalled,
|
||||
ipaRecalled,
|
||||
layerAllDeleted,
|
||||
layerRecalled,
|
||||
@ -43,8 +43,8 @@ import type {
|
||||
CanvasControlAdapterState,
|
||||
CanvasIPAdapterState,
|
||||
CanvasLayerState,
|
||||
LoRA,
|
||||
CanvasRegionalGuidanceState,
|
||||
LoRA,
|
||||
} from 'features/controlLayers/store/types';
|
||||
import { setHrfEnabled, setHrfMethod, setHrfStrength } from 'features/hrf/store/hrfSlice';
|
||||
import type {
|
||||
@ -271,7 +271,7 @@ const recallCA: MetadataRecallFunc<CanvasControlAdapterState> = async (ca) => {
|
||||
}
|
||||
// No clobber
|
||||
clone.id = getCAId(uuidv4());
|
||||
dispatch(caRecalled({ data: clone }));
|
||||
// dispatch(caRecalled({ data: clone }));
|
||||
return;
|
||||
};
|
||||
|
||||
|
@ -26,10 +26,11 @@ export const addControlAdapters = async (
|
||||
const layersWithValidControlAdapters = layers
|
||||
.filter((layer) => layer.isEnabled)
|
||||
.filter((layer) => doesLayerHaveValidControlAdapter(layer, base));
|
||||
|
||||
for (const layer of layersWithValidControlAdapters) {
|
||||
const adapter = manager.layers.get(layer.id);
|
||||
assert(adapter, 'Adapter not found');
|
||||
const imageDTO = await adapter.renderer.getImageDTO({ rect: bbox, is_intermediate: true, category: 'control' });
|
||||
const imageDTO = await adapter.renderer.rasterize(bbox);
|
||||
if (layer.controlAdapter.type === 'controlnet') {
|
||||
await addControlNetToGraph(g, layer, imageDTO, denoise);
|
||||
} else {
|
||||
|
@ -22,7 +22,7 @@ export const addInpaint = async (
|
||||
denoise.denoising_start = denoising_start;
|
||||
|
||||
const initialImage = await manager.getCompositeLayerImageDTO(bbox.rect);
|
||||
const maskImage = await manager.getInpaintMaskImageDTO(bbox.rect);
|
||||
const maskImage = await manager.inpaintMask.renderer.rasterize(bbox.rect);
|
||||
|
||||
if (!isEqual(scaledSize, originalSize)) {
|
||||
// Scale before processing requires some resizing
|
||||
|
@ -23,7 +23,7 @@ export const addOutpaint = async (
|
||||
denoise.denoising_start = denoising_start;
|
||||
|
||||
const initialImage = await manager.getCompositeLayerImageDTO(bbox.rect);
|
||||
const maskImage = await manager.getInpaintMaskImageDTO(bbox.rect);
|
||||
const maskImage = await manager.inpaintMask.renderer.rasterize(bbox.rect);
|
||||
const infill = getInfill(g, compositing);
|
||||
|
||||
if (!isEqual(scaledSize, originalSize)) {
|
||||
|
@ -43,15 +43,16 @@ export const addRegions = async (
|
||||
const validRegions = regions.filter((rg) => isValidRegion(rg, base));
|
||||
|
||||
for (const region of validRegions) {
|
||||
// Upload the mask image, or get the cached image if it exists
|
||||
const { image_name } = await manager.getRegionMaskImageDTO(region.id, bbox);
|
||||
const adapter = manager.regions.get(region.id);
|
||||
assert(adapter, 'Adapter not found');
|
||||
const imageDTO = await adapter.renderer.rasterize(bbox);
|
||||
|
||||
// The main mask-to-tensor node
|
||||
const maskToTensor = g.addNode({
|
||||
id: `${PROMPT_REGION_MASK_TO_TENSOR_PREFIX}_${region.id}`,
|
||||
type: 'alpha_mask_to_tensor',
|
||||
image: {
|
||||
image_name,
|
||||
image_name: imageDTO.image_name,
|
||||
},
|
||||
});
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user