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 { addDeleteBoardAndImagesFulfilledListener } from 'app/store/middleware/listenerMiddleware/listeners/boardAndImagesDeleted';
|
||||||
import { addBoardIdSelectedListener } from 'app/store/middleware/listenerMiddleware/listeners/boardIdSelected';
|
import { addBoardIdSelectedListener } from 'app/store/middleware/listenerMiddleware/listeners/boardIdSelected';
|
||||||
import { addBulkDownloadListeners } from 'app/store/middleware/listenerMiddleware/listeners/bulkDownload';
|
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 { addEnqueueRequestedLinear } from 'app/store/middleware/listenerMiddleware/listeners/enqueueRequestedLinear';
|
||||||
import { addEnqueueRequestedNodes } from 'app/store/middleware/listenerMiddleware/listeners/enqueueRequestedNodes';
|
import { addEnqueueRequestedNodes } from 'app/store/middleware/listenerMiddleware/listeners/enqueueRequestedNodes';
|
||||||
import { addGalleryImageClickedListener } from 'app/store/middleware/listenerMiddleware/listeners/galleryImageClicked';
|
import { addGalleryImageClickedListener } from 'app/store/middleware/listenerMiddleware/listeners/galleryImageClicked';
|
||||||
@ -133,4 +132,4 @@ addAdHocPostProcessingRequestedListener(startAppListening);
|
|||||||
addDynamicPromptsListener(startAppListening);
|
addDynamicPromptsListener(startAppListening);
|
||||||
|
|
||||||
addSetDefaultSettingsListener(startAppListening);
|
addSetDefaultSettingsListener(startAppListening);
|
||||||
addControlAdapterPreprocessor(startAppListening);
|
// addControlAdapterPreprocessor(startAppListening);
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
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 { getImageUsage } from 'features/deleteImageModal/store/selectors';
|
||||||
import { nodeEditorReset } from 'features/nodes/store/nodesSlice';
|
import { nodeEditorReset } from 'features/nodes/store/nodesSlice';
|
||||||
import { imagesApi } from 'services/api/endpoints/images';
|
import { imagesApi } from 'services/api/endpoints/images';
|
||||||
@ -14,7 +14,7 @@ export const addDeleteBoardAndImagesFulfilledListener = (startAppListening: AppS
|
|||||||
|
|
||||||
let wereLayersReset = false;
|
let wereLayersReset = false;
|
||||||
let wasNodeEditorReset = false;
|
let wasNodeEditorReset = false;
|
||||||
let wereControlAdaptersReset = false;
|
const wereControlAdaptersReset = false;
|
||||||
let wereIPAdaptersReset = false;
|
let wereIPAdaptersReset = false;
|
||||||
|
|
||||||
const { nodes, canvasV2 } = getState();
|
const { nodes, canvasV2 } = getState();
|
||||||
@ -31,10 +31,10 @@ export const addDeleteBoardAndImagesFulfilledListener = (startAppListening: AppS
|
|||||||
wasNodeEditorReset = true;
|
wasNodeEditorReset = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (imageUsage.isControlAdapterImage && !wereControlAdaptersReset) {
|
// if (imageUsage.isControlAdapterImage && !wereControlAdaptersReset) {
|
||||||
dispatch(caAllDeleted());
|
// dispatch(caAllDeleted());
|
||||||
wereControlAdaptersReset = true;
|
// wereControlAdaptersReset = true;
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (imageUsage.isIPAdapterImage && !wereIPAdaptersReset) {
|
if (imageUsage.isIPAdapterImage && !wereIPAdaptersReset) {
|
||||||
dispatch(ipaAllDeleted());
|
dispatch(ipaAllDeleted());
|
||||||
|
@ -1,12 +1,7 @@
|
|||||||
import { logger } from 'app/logging/logger';
|
import { logger } from 'app/logging/logger';
|
||||||
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
||||||
import type { AppDispatch, RootState } from 'app/store/store';
|
import type { AppDispatch, RootState } from 'app/store/store';
|
||||||
import {
|
import { entityDeleted, ipaImageChanged } from 'features/controlLayers/store/canvasV2Slice';
|
||||||
caImageChanged,
|
|
||||||
caProcessedImageChanged,
|
|
||||||
entityDeleted,
|
|
||||||
ipaImageChanged,
|
|
||||||
} from 'features/controlLayers/store/canvasV2Slice';
|
|
||||||
import { imageDeletionConfirmed } from 'features/deleteImageModal/store/actions';
|
import { imageDeletionConfirmed } from 'features/deleteImageModal/store/actions';
|
||||||
import { isModalOpenChanged } from 'features/deleteImageModal/store/slice';
|
import { isModalOpenChanged } from 'features/deleteImageModal/store/slice';
|
||||||
import { selectListImagesQueryArgs } from 'features/gallery/store/gallerySelectors';
|
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) => {
|
// const deleteControlAdapterImages = (state: RootState, dispatch: AppDispatch, imageDTO: ImageDTO) => {
|
||||||
state.canvasV2.controlAdapters.entities.forEach(({ id, imageObject, processedImageObject }) => {
|
// state.canvasV2.controlAdapters.entities.forEach(({ id, imageObject, processedImageObject }) => {
|
||||||
if (imageObject?.image.image_name === imageDTO.image_name || processedImageObject?.image.image_name === imageDTO.image_name) {
|
// if (
|
||||||
dispatch(caImageChanged({ id, imageDTO: null }));
|
// imageObject?.image.image_name === imageDTO.image_name ||
|
||||||
dispatch(caProcessedImageChanged({ id, imageDTO: null }));
|
// 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) => {
|
const deleteIPAdapterImages = (state: RootState, dispatch: AppDispatch, imageDTO: ImageDTO) => {
|
||||||
state.canvasV2.ipAdapters.entities.forEach(({ id, imageObject }) => {
|
state.canvasV2.ipAdapters.entities.forEach(({ id, imageObject }) => {
|
||||||
@ -120,7 +118,7 @@ export const addImageDeletionListeners = (startAppListening: AppStartListening)
|
|||||||
}
|
}
|
||||||
|
|
||||||
deleteNodesImages(state, dispatch, imageDTO);
|
deleteNodesImages(state, dispatch, imageDTO);
|
||||||
deleteControlAdapterImages(state, dispatch, imageDTO);
|
// deleteControlAdapterImages(state, dispatch, imageDTO);
|
||||||
deleteIPAdapterImages(state, dispatch, imageDTO);
|
deleteIPAdapterImages(state, dispatch, imageDTO);
|
||||||
deleteLayerImages(state, dispatch, imageDTO);
|
deleteLayerImages(state, dispatch, imageDTO);
|
||||||
} catch {
|
} catch {
|
||||||
@ -161,7 +159,7 @@ export const addImageDeletionListeners = (startAppListening: AppStartListening)
|
|||||||
|
|
||||||
imageDTOs.forEach((imageDTO) => {
|
imageDTOs.forEach((imageDTO) => {
|
||||||
deleteNodesImages(state, dispatch, imageDTO);
|
deleteNodesImages(state, dispatch, imageDTO);
|
||||||
deleteControlAdapterImages(state, dispatch, imageDTO);
|
// deleteControlAdapterImages(state, dispatch, imageDTO);
|
||||||
deleteIPAdapterImages(state, dispatch, imageDTO);
|
deleteIPAdapterImages(state, dispatch, imageDTO);
|
||||||
deleteLayerImages(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 type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
||||||
import { parseify } from 'common/util/serialize';
|
import { parseify } from 'common/util/serialize';
|
||||||
import {
|
import {
|
||||||
caImageChanged,
|
|
||||||
ipaImageChanged,
|
ipaImageChanged,
|
||||||
layerAdded,
|
layerAdded,
|
||||||
rgIPAdapterImageChanged,
|
rgIPAdapterImageChanged,
|
||||||
@ -60,18 +59,18 @@ export const addImageDroppedListener = (startAppListening: AppStartListening) =>
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// /**
|
||||||
* Image dropped on Control Adapter Layer
|
// * Image dropped on Control Adapter Layer
|
||||||
*/
|
// */
|
||||||
if (
|
// if (
|
||||||
overData.actionType === 'SET_CA_IMAGE' &&
|
// overData.actionType === 'SET_CA_IMAGE' &&
|
||||||
activeData.payloadType === 'IMAGE_DTO' &&
|
// activeData.payloadType === 'IMAGE_DTO' &&
|
||||||
activeData.payload.imageDTO
|
// activeData.payload.imageDTO
|
||||||
) {
|
// ) {
|
||||||
const { id } = overData.context;
|
// const { id } = overData.context;
|
||||||
dispatch(caImageChanged({ id, imageDTO: activeData.payload.imageDTO }));
|
// dispatch(caImageChanged({ id, imageDTO: activeData.payload.imageDTO }));
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Image dropped on IP Adapter Layer
|
* Image dropped on IP Adapter Layer
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { logger } from 'app/logging/logger';
|
import { logger } from 'app/logging/logger';
|
||||||
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
||||||
import { caImageChanged, ipaImageChanged, rgIPAdapterImageChanged } from 'features/controlLayers/store/canvasV2Slice';
|
import { ipaImageChanged, rgIPAdapterImageChanged } from 'features/controlLayers/store/canvasV2Slice';
|
||||||
import { selectListBoardsQueryArgs } from 'features/gallery/store/gallerySelectors';
|
import { selectListBoardsQueryArgs } from 'features/gallery/store/gallerySelectors';
|
||||||
import { fieldImageValueChanged } from 'features/nodes/store/nodesSlice';
|
import { fieldImageValueChanged } from 'features/nodes/store/nodesSlice';
|
||||||
import { upscaleInitialImageChanged } from 'features/parameters/store/upscaleSlice';
|
import { upscaleInitialImageChanged } from 'features/parameters/store/upscaleSlice';
|
||||||
@ -79,12 +79,12 @@ export const addImageUploadedFulfilledListener = (startAppListening: AppStartLis
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (postUploadAction?.type === 'SET_CA_IMAGE') {
|
// if (postUploadAction?.type === 'SET_CA_IMAGE') {
|
||||||
const { id } = postUploadAction;
|
// const { id } = postUploadAction;
|
||||||
dispatch(caImageChanged({ id, imageDTO }));
|
// dispatch(caImageChanged({ id, imageDTO }));
|
||||||
toast({ ...DEFAULT_UPLOADED_TOAST, description: t('toast.setControlImage') });
|
// toast({ ...DEFAULT_UPLOADED_TOAST, description: t('toast.setControlImage') });
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (postUploadAction?.type === 'SET_IPA_IMAGE') {
|
if (postUploadAction?.type === 'SET_IPA_IMAGE') {
|
||||||
const { id } = postUploadAction;
|
const { id } = postUploadAction;
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { logger } from 'app/logging/logger';
|
import { logger } from 'app/logging/logger';
|
||||||
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
||||||
import {
|
import {
|
||||||
entityIsEnabledToggled,
|
|
||||||
loraDeleted,
|
loraDeleted,
|
||||||
modelChanged,
|
modelChanged,
|
||||||
vaeSelected,
|
vaeSelected,
|
||||||
@ -50,14 +49,14 @@ export const addModelSelectedListener = (startAppListening: AppStartListening) =
|
|||||||
}
|
}
|
||||||
|
|
||||||
// handle incompatible controlnets
|
// handle incompatible controlnets
|
||||||
state.canvasV2.controlAdapters.entities.forEach((ca) => {
|
// state.canvasV2.controlAdapters.entities.forEach((ca) => {
|
||||||
if (ca.model?.base !== newBaseModel) {
|
// if (ca.model?.base !== newBaseModel) {
|
||||||
modelsCleared += 1;
|
// modelsCleared += 1;
|
||||||
if (ca.isEnabled) {
|
// if (ca.isEnabled) {
|
||||||
dispatch(entityIsEnabledToggled({ entityIdentifier: { id: ca.id, type: 'control_adapter' } }));
|
// dispatch(entityIsEnabledToggled({ entityIdentifier: { id: ca.id, type: 'control_adapter' } }));
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
});
|
// });
|
||||||
|
|
||||||
if (modelsCleared > 0) {
|
if (modelsCleared > 0) {
|
||||||
toast({
|
toast({
|
||||||
|
@ -5,7 +5,6 @@ import type { JSONObject } from 'common/types';
|
|||||||
import {
|
import {
|
||||||
bboxHeightChanged,
|
bboxHeightChanged,
|
||||||
bboxWidthChanged,
|
bboxWidthChanged,
|
||||||
caModelChanged,
|
|
||||||
ipaModelChanged,
|
ipaModelChanged,
|
||||||
loraDeleted,
|
loraDeleted,
|
||||||
modelChanged,
|
modelChanged,
|
||||||
@ -21,7 +20,6 @@ import type { Logger } from 'roarr';
|
|||||||
import { modelConfigsAdapterSelectors, modelsApi } from 'services/api/endpoints/models';
|
import { modelConfigsAdapterSelectors, modelsApi } from 'services/api/endpoints/models';
|
||||||
import type { AnyModelConfig } from 'services/api/types';
|
import type { AnyModelConfig } from 'services/api/types';
|
||||||
import {
|
import {
|
||||||
isControlNetOrT2IAdapterModelConfig,
|
|
||||||
isIPAdapterModelConfig,
|
isIPAdapterModelConfig,
|
||||||
isLoRAModelConfig,
|
isLoRAModelConfig,
|
||||||
isNonRefinerMainModelConfig,
|
isNonRefinerMainModelConfig,
|
||||||
@ -171,14 +169,14 @@ const handleLoRAModels: ModelHandler = (models, state, dispatch, _log) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleControlAdapterModels: ModelHandler = (models, state, dispatch, _log) => {
|
const handleControlAdapterModels: ModelHandler = (models, state, dispatch, _log) => {
|
||||||
const caModels = models.filter(isControlNetOrT2IAdapterModelConfig);
|
// const caModels = models.filter(isControlNetOrT2IAdapterModelConfig);
|
||||||
state.canvasV2.controlAdapters.entities.forEach((ca) => {
|
// state.canvasV2.controlAdapters.entities.forEach((ca) => {
|
||||||
const isModelAvailable = caModels.some((m) => m.key === ca.model?.key);
|
// const isModelAvailable = caModels.some((m) => m.key === ca.model?.key);
|
||||||
if (isModelAvailable) {
|
// if (isModelAvailable) {
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
dispatch(caModelChanged({ id: ca.id, modelConfig: null }));
|
// dispatch(caModelChanged({ id: ca.id, modelConfig: null }));
|
||||||
});
|
// });
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleIPAdapterModels: ModelHandler = (models, state, dispatch, _log) => {
|
const handleIPAdapterModels: ModelHandler = (models, state, dispatch, _log) => {
|
||||||
|
@ -125,41 +125,41 @@ const createSelector = (templates: Templates) =>
|
|||||||
reasons.push({ content: i18n.t('parameters.invoke.noModelSelected') });
|
reasons.push({ content: i18n.t('parameters.invoke.noModelSelected') });
|
||||||
}
|
}
|
||||||
|
|
||||||
canvasV2.controlAdapters.entities
|
// canvasV2.controlAdapters.entities
|
||||||
.filter((ca) => ca.isEnabled)
|
// .filter((ca) => ca.isEnabled)
|
||||||
.forEach((ca, i) => {
|
// .forEach((ca, i) => {
|
||||||
const layerLiteral = i18n.t('controlLayers.layers_one');
|
// const layerLiteral = i18n.t('controlLayers.layers_one');
|
||||||
const layerNumber = i + 1;
|
// const layerNumber = i + 1;
|
||||||
const layerType = i18n.t(LAYER_TYPE_TO_TKEY[ca.type]);
|
// const layerType = i18n.t(LAYER_TYPE_TO_TKEY[ca.type]);
|
||||||
const prefix = `${layerLiteral} #${layerNumber} (${layerType})`;
|
// const prefix = `${layerLiteral} #${layerNumber} (${layerType})`;
|
||||||
const problems: string[] = [];
|
// const problems: string[] = [];
|
||||||
// Must have model
|
// // Must have model
|
||||||
if (!ca.model) {
|
// if (!ca.model) {
|
||||||
problems.push(i18n.t('parameters.invoke.layer.controlAdapterNoModelSelected'));
|
// problems.push(i18n.t('parameters.invoke.layer.controlAdapterNoModelSelected'));
|
||||||
}
|
// }
|
||||||
// Model base must match
|
// // Model base must match
|
||||||
if (ca.model?.base !== model?.base) {
|
// if (ca.model?.base !== model?.base) {
|
||||||
problems.push(i18n.t('parameters.invoke.layer.controlAdapterIncompatibleBaseModel'));
|
// 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
|
// // Must have a control image OR, if it has a processor, it must have a processed image
|
||||||
if (!ca.imageObject) {
|
// if (!ca.imageObject) {
|
||||||
problems.push(i18n.t('parameters.invoke.layer.controlAdapterNoImageSelected'));
|
// problems.push(i18n.t('parameters.invoke.layer.controlAdapterNoImageSelected'));
|
||||||
} else if (ca.processorConfig && !ca.processedImageObject) {
|
// } else if (ca.processorConfig && !ca.processedImageObject) {
|
||||||
problems.push(i18n.t('parameters.invoke.layer.controlAdapterImageNotProcessed'));
|
// problems.push(i18n.t('parameters.invoke.layer.controlAdapterImageNotProcessed'));
|
||||||
}
|
// }
|
||||||
// T2I Adapters require images have dimensions that are multiples of 64 (SD1.5) or 32 (SDXL)
|
// // T2I Adapters require images have dimensions that are multiples of 64 (SD1.5) or 32 (SDXL)
|
||||||
if (ca.adapterType === 't2i_adapter') {
|
// if (ca.adapterType === 't2i_adapter') {
|
||||||
const multiple = model?.base === 'sdxl' ? 32 : 64;
|
// const multiple = model?.base === 'sdxl' ? 32 : 64;
|
||||||
if (bbox.rect.width % multiple !== 0 || bbox.rect.height % multiple !== 0) {
|
// if (bbox.rect.width % multiple !== 0 || bbox.rect.height % multiple !== 0) {
|
||||||
problems.push(i18n.t('parameters.invoke.layer.t2iAdapterIncompatibleDimensions', { multiple }));
|
// problems.push(i18n.t('parameters.invoke.layer.t2iAdapterIncompatibleDimensions', { multiple }));
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (problems.length) {
|
// if (problems.length) {
|
||||||
const content = upperFirst(problems.join(', '));
|
// const content = upperFirst(problems.join(', '));
|
||||||
reasons.push({ prefix, content });
|
// reasons.push({ prefix, content });
|
||||||
}
|
// }
|
||||||
});
|
// });
|
||||||
|
|
||||||
canvasV2.ipAdapters.entities
|
canvasV2.ipAdapters.entities
|
||||||
.filter((ipa) => ipa.isEnabled)
|
.filter((ipa) => ipa.isEnabled)
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { Flex } from '@invoke-ai/ui-library';
|
import { Flex } from '@invoke-ai/ui-library';
|
||||||
import ScrollableContent from 'common/components/OverlayScrollbars/ScrollableContent';
|
import ScrollableContent from 'common/components/OverlayScrollbars/ScrollableContent';
|
||||||
import { ControlAdapterList } from 'features/controlLayers/components/ControlAdapter/ControlAdapterList';
|
|
||||||
import { InpaintMask } from 'features/controlLayers/components/InpaintMask/InpaintMask';
|
import { InpaintMask } from 'features/controlLayers/components/InpaintMask/InpaintMask';
|
||||||
import { IPAdapterList } from 'features/controlLayers/components/IPAdapter/IPAdapterList';
|
import { IPAdapterList } from 'features/controlLayers/components/IPAdapter/IPAdapterList';
|
||||||
import { LayerEntityList } from 'features/controlLayers/components/Layer/LayerEntityList';
|
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">
|
<Flex flexDir="column" gap={2} data-testid="control-layers-layer-list">
|
||||||
<InpaintMask />
|
<InpaintMask />
|
||||||
<RegionalGuidanceEntityList />
|
<RegionalGuidanceEntityList />
|
||||||
<ControlAdapterList />
|
|
||||||
<IPAdapterList />
|
<IPAdapterList />
|
||||||
<LayerEntityList />
|
<LayerEntityList />
|
||||||
</Flex>
|
</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 {
|
import {
|
||||||
|
Button,
|
||||||
Checkbox,
|
Checkbox,
|
||||||
Flex,
|
Flex,
|
||||||
FormControl,
|
FormControl,
|
||||||
@ -11,7 +12,11 @@ import {
|
|||||||
} from '@invoke-ai/ui-library';
|
} from '@invoke-ai/ui-library';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { MaskOpacity } from 'features/controlLayers/components/MaskOpacity';
|
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 type { ChangeEvent } from 'react';
|
||||||
import { memo, useCallback } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@ -30,6 +35,9 @@ const ControlLayersSettingsPopover = () => {
|
|||||||
(e: ChangeEvent<HTMLInputElement>) => dispatch(clipToBboxChanged(e.target.checked)),
|
(e: ChangeEvent<HTMLInputElement>) => dispatch(clipToBboxChanged(e.target.checked)),
|
||||||
[dispatch]
|
[dispatch]
|
||||||
);
|
);
|
||||||
|
const invalidateRasterizationCaches = useCallback(() => {
|
||||||
|
dispatch(rasterizationCachesInvalidated());
|
||||||
|
}, [dispatch]);
|
||||||
return (
|
return (
|
||||||
<Popover isLazy>
|
<Popover isLazy>
|
||||||
<PopoverTrigger>
|
<PopoverTrigger>
|
||||||
@ -47,6 +55,9 @@ const ControlLayersSettingsPopover = () => {
|
|||||||
<FormLabel flexGrow={1}>{t('unifiedCanvas.clipToBbox')}</FormLabel>
|
<FormLabel flexGrow={1}>{t('unifiedCanvas.clipToBbox')}</FormLabel>
|
||||||
<Checkbox isChecked={clipToBbox} onChange={onChangeClipToBbox} />
|
<Checkbox isChecked={clipToBbox} onChange={onChangeClipToBbox} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
<Button onClick={invalidateRasterizationCaches} size="sm">
|
||||||
|
Invalidate Rasterization Caches
|
||||||
|
</Button>
|
||||||
</Flex>
|
</Flex>
|
||||||
</PopoverBody>
|
</PopoverBody>
|
||||||
</PopoverContent>
|
</PopoverContent>
|
||||||
|
@ -11,7 +11,7 @@ export const DeleteAllLayersButton = memo(() => {
|
|||||||
const entityCount = useAppSelector((s) => {
|
const entityCount = useAppSelector((s) => {
|
||||||
return (
|
return (
|
||||||
s.canvasV2.regions.entities.length +
|
s.canvasV2.regions.entities.length +
|
||||||
s.canvasV2.controlAdapters.entities.length +
|
// s.canvasV2.controlAdapters.entities.length +
|
||||||
s.canvasV2.ipAdapters.entities.length +
|
s.canvasV2.ipAdapters.entities.length +
|
||||||
s.canvasV2.layers.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 type { CanvasEntityIdentifier } from 'features/controlLayers/store/types';
|
||||||
import { memo, useMemo } from 'react';
|
import { memo, useMemo } from 'react';
|
||||||
|
|
||||||
import { LayerOpacity } from './LayerOpacity';
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
id: string;
|
id: string;
|
||||||
};
|
};
|
||||||
@ -26,7 +24,6 @@ export const Layer = memo(({ id }: Props) => {
|
|||||||
<CanvasEntityEnabledToggle />
|
<CanvasEntityEnabledToggle />
|
||||||
<CanvasEntityTitle />
|
<CanvasEntityTitle />
|
||||||
<Spacer />
|
<Spacer />
|
||||||
<LayerOpacity />
|
|
||||||
<LayerActionsMenu />
|
<LayerActionsMenu />
|
||||||
<CanvasEntityDeleteButton />
|
<CanvasEntityDeleteButton />
|
||||||
</CanvasEntityHeader>
|
</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),
|
index: canvasV2.layers.entities.findIndex((entity) => entity.id === id),
|
||||||
count: canvasV2.layers.entities.length,
|
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') {
|
} else if (type === 'regional_guidance') {
|
||||||
return {
|
return {
|
||||||
index: canvasV2.regions.entities.findIndex((entity) => entity.id === id),
|
index: canvasV2.regions.entities.findIndex((entity) => entity.id === id),
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { deepClone } from 'common/util/deepClone';
|
import { deepClone } from 'common/util/deepClone';
|
||||||
import { caAdded, ipaAdded, rgIPAdapterAdded } from 'features/controlLayers/store/canvasV2Slice';
|
import { ipaAdded, rgIPAdapterAdded } from 'features/controlLayers/store/canvasV2Slice';
|
||||||
import {
|
import {
|
||||||
IMAGE_FILTERS,
|
IMAGE_FILTERS,
|
||||||
initialControlNetV2,
|
initialControlNetV2,
|
||||||
@ -37,7 +37,7 @@ export const useAddCALayer = () => {
|
|||||||
const initialConfig = deepClone(model.type === 'controlnet' ? initialControlNetV2 : initialT2IAdapterV2);
|
const initialConfig = deepClone(model.type === 'controlnet' ? initialControlNetV2 : initialT2IAdapterV2);
|
||||||
const config = { ...initialConfig, model: zModelIdentifierField.parse(model), processorConfig };
|
const config = { ...initialConfig, model: zModelIdentifierField.parse(model), processorConfig };
|
||||||
|
|
||||||
dispatch(caAdded({ config }));
|
// dispatch(caAdded({ config }));
|
||||||
}, [dispatch, model, baseModel]);
|
}, [dispatch, model, baseModel]);
|
||||||
|
|
||||||
return [addCALayer, isDisabled] as const;
|
return [addCALayer, isDisabled] as const;
|
||||||
|
@ -100,7 +100,12 @@ export class CanvasFilter {
|
|||||||
this.manager.stateApi.rasterizeEntity({
|
this.manager.stateApi.rasterizeEntity({
|
||||||
entityIdentifier: this.parent.getEntityIdentifier(),
|
entityIdentifier: this.parent.getEntityIdentifier(),
|
||||||
imageObject: this.imageState,
|
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.parent.renderer.showObjects();
|
||||||
this.manager.stateApi.$filteringEntity.set(null);
|
this.manager.stateApi.$filteringEntity.set(null);
|
||||||
|
@ -9,20 +9,27 @@ import {
|
|||||||
konvaNodeToBlob,
|
konvaNodeToBlob,
|
||||||
konvaNodeToImageData,
|
konvaNodeToImageData,
|
||||||
nanoid,
|
nanoid,
|
||||||
|
previewBlob,
|
||||||
} from 'features/controlLayers/konva/util';
|
} from 'features/controlLayers/konva/util';
|
||||||
import type { Extents, ExtentsResult, GetBboxTask, WorkerLogMessage } from 'features/controlLayers/konva/worker';
|
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 { isValidLayerWithoutControlAdapter } from 'features/nodes/util/graph/generation/addLayers';
|
||||||
import type Konva from 'konva';
|
import type Konva from 'konva';
|
||||||
import { clamp } from 'lodash-es';
|
import { clamp, isEqual } from 'lodash-es';
|
||||||
import { atom } from 'nanostores';
|
import { atom } from 'nanostores';
|
||||||
import type { Logger } from 'roarr';
|
import type { Logger } from 'roarr';
|
||||||
import { getImageDTO, uploadImage } from 'services/api/endpoints/images';
|
import { getImageDTO, uploadImage } from 'services/api/endpoints/images';
|
||||||
import type { ImageDTO } from 'services/api/types';
|
import type { ImageDTO } from 'services/api/types';
|
||||||
import { assert } from 'tsafe';
|
|
||||||
|
|
||||||
import { CanvasBackground } from './CanvasBackground';
|
import { CanvasBackground } from './CanvasBackground';
|
||||||
import { CanvasControlAdapter } from './CanvasControlAdapter';
|
import type { CanvasControlAdapter } from './CanvasControlAdapter';
|
||||||
import { CanvasLayerAdapter } from './CanvasLayerAdapter';
|
import { CanvasLayerAdapter } from './CanvasLayerAdapter';
|
||||||
import { CanvasMaskAdapter } from './CanvasMaskAdapter';
|
import { CanvasMaskAdapter } from './CanvasMaskAdapter';
|
||||||
import { CanvasPreview } from './CanvasPreview';
|
import { CanvasPreview } from './CanvasPreview';
|
||||||
@ -144,40 +151,15 @@ export class CanvasManager {
|
|||||||
this._worker.postMessage(task, [data.buffer]);
|
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() {
|
arrangeEntities() {
|
||||||
const { getLayersState, getControlAdaptersState, getRegionsState } = this.stateApi;
|
const { getLayersState, getRegionsState } = this.stateApi;
|
||||||
const layers = getLayersState().entities;
|
const layers = getLayersState().entities;
|
||||||
const controlAdapters = getControlAdaptersState().entities;
|
|
||||||
const regions = getRegionsState().entities;
|
const regions = getRegionsState().entities;
|
||||||
let zIndex = 0;
|
let zIndex = 0;
|
||||||
this.background.konva.layer.zIndex(++zIndex);
|
this.background.konva.layer.zIndex(++zIndex);
|
||||||
for (const layer of layers) {
|
for (const layer of layers) {
|
||||||
this.layers.get(layer.id)?.konva.layer.zIndex(++zIndex);
|
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) {
|
for (const rg of regions) {
|
||||||
this.regions.get(rg.id)?.konva.layer.zIndex(++zIndex);
|
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.$toolState.set(state.tool);
|
||||||
this.stateApi.$selectedEntityIdentifier.set(state.selectedEntityIdentifier);
|
this.stateApi.$selectedEntityIdentifier.set(state.selectedEntityIdentifier);
|
||||||
this.stateApi.$selectedEntity.set(this.stateApi.getSelectedEntity());
|
this.stateApi.$selectedEntity.set(this.stateApi.getSelectedEntity());
|
||||||
@ -382,12 +354,7 @@ export class CanvasManager {
|
|||||||
await this.preview.bbox.render();
|
await this.preview.bbox.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (this._isFirstRender || state.layers !== this._prevState.layers || state.regions !== this._prevState.regions) {
|
||||||
this._isFirstRender ||
|
|
||||||
state.layers !== this._prevState.layers ||
|
|
||||||
state.controlAdapters !== this._prevState.controlAdapters ||
|
|
||||||
state.regions !== this._prevState.regions
|
|
||||||
) {
|
|
||||||
// this.log.debug('Updating entity bboxes');
|
// this.log.debug('Updating entity bboxes');
|
||||||
// debouncedUpdateBboxes(stage, canvasV2.layers, canvasV2.controlAdapters, canvasV2.regions, onBboxChanged);
|
// debouncedUpdateBboxes(stage, canvasV2.layers, canvasV2.controlAdapters, canvasV2.regions, onBboxChanged);
|
||||||
}
|
}
|
||||||
@ -400,7 +367,6 @@ export class CanvasManager {
|
|||||||
if (
|
if (
|
||||||
this._isFirstRender ||
|
this._isFirstRender ||
|
||||||
state.layers.entities !== this._prevState.layers.entities ||
|
state.layers.entities !== this._prevState.layers.entities ||
|
||||||
state.controlAdapters.entities !== this._prevState.controlAdapters.entities ||
|
|
||||||
state.regions.entities !== this._prevState.regions.entities ||
|
state.regions.entities !== this._prevState.regions.entities ||
|
||||||
state.inpaintMask !== this._prevState.inpaintMask ||
|
state.inpaintMask !== this._prevState.inpaintMask ||
|
||||||
state.selectedEntityIdentifier?.id !== this._prevState.selectedEntityIdentifier?.id
|
state.selectedEntityIdentifier?.id !== this._prevState.selectedEntityIdentifier?.id
|
||||||
@ -569,45 +535,43 @@ export class CanvasManager {
|
|||||||
return konvaNodeToImageData(this.getCompositeLayerStageClone(), rect);
|
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 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;
|
return imageDTO;
|
||||||
};
|
};
|
||||||
|
|
||||||
getInpaintMaskBlob = (rect?: Rect): Promise<Blob> => {
|
getInpaintMaskBlob = (rect?: Rect): Promise<Blob> => {
|
||||||
return this.inpaintMask.renderer.getBlob({ rect });
|
return this.inpaintMask.renderer.getBlob(rect);
|
||||||
};
|
};
|
||||||
|
|
||||||
getInpaintMaskImageData = (rect?: Rect): ImageData => {
|
getInpaintMaskImageData = (rect?: Rect): ImageData => {
|
||||||
return this.inpaintMask.renderer.getImageData({ rect });
|
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);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
getGenerationMode(): GenerationMode {
|
getGenerationMode(): GenerationMode {
|
||||||
|
@ -14,14 +14,16 @@ import type {
|
|||||||
CanvasEraserLineState,
|
CanvasEraserLineState,
|
||||||
CanvasImageState,
|
CanvasImageState,
|
||||||
CanvasRectState,
|
CanvasRectState,
|
||||||
|
ImageCache,
|
||||||
Rect,
|
Rect,
|
||||||
RgbColor,
|
RgbColor,
|
||||||
} from 'features/controlLayers/store/types';
|
} from 'features/controlLayers/store/types';
|
||||||
import { imageDTOToImageObject } from 'features/controlLayers/store/types';
|
import { imageDTOToImageObject } from 'features/controlLayers/store/types';
|
||||||
import Konva from 'konva';
|
import Konva from 'konva';
|
||||||
|
import { isEqual } from 'lodash-es';
|
||||||
import type { Logger } from 'roarr';
|
import type { Logger } from 'roarr';
|
||||||
import { getImageDTO, uploadImage } from 'services/api/endpoints/images';
|
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';
|
import { assert } from 'tsafe';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -62,12 +64,6 @@ export class CanvasObjectRenderer {
|
|||||||
*/
|
*/
|
||||||
renderers: Map<string, AnyObjectRenderer> = new Map();
|
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.
|
* A object containing singleton Konva nodes.
|
||||||
*/
|
*/
|
||||||
@ -168,19 +164,6 @@ export class CanvasObjectRenderer {
|
|||||||
didRender = (await this.renderObject(this.buffer)) || didRender;
|
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;
|
return didRender;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -376,29 +359,37 @@ export class CanvasObjectRenderer {
|
|||||||
return this.renderers.size > 0 || this.buffer !== null;
|
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
|
* Rasterizes the parent entity. If the entity has a rasterization cache for the given rect, the cached image is
|
||||||
* validating that it exists on the server.
|
* 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
|
* The rasterization cache is reset when the entity's state changes. The buffer object is not considered part of the
|
||||||
* entity's objects for this purpose.
|
* 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.
|
* @returns A promise that resolves to the rasterized image DTO.
|
||||||
*/
|
*/
|
||||||
rasterize = async (): Promise<ImageDTO> => {
|
rasterize = async (rect?: Rect): Promise<ImageDTO> => {
|
||||||
this.log.debug('Rasterizing entity');
|
rect = rect ?? this.parent.transformer.getRelativeRect();
|
||||||
|
|
||||||
let imageDTO: ImageDTO | null = null;
|
let imageDTO: ImageDTO | null = null;
|
||||||
if (this.rasterizedImageCache) {
|
const rasterizedImageCache = this.getRasterizedImageCache(rect);
|
||||||
imageDTO = await getImageDTO(this.rasterizedImageCache);
|
|
||||||
|
if (rasterizedImageCache) {
|
||||||
|
imageDTO = await getImageDTO(rasterizedImageCache.imageName);
|
||||||
|
if (imageDTO) {
|
||||||
|
this.log.trace({ rect, rasterizedImageCache, imageDTO }, 'Using cached rasterized image');
|
||||||
|
return imageDTO;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (imageDTO) {
|
this.log.trace({ rect }, 'Rasterizing entity');
|
||||||
return imageDTO;
|
|
||||||
}
|
|
||||||
|
|
||||||
const rect = this.parent.transformer.getRelativeRect();
|
const blob = await this.getBlob(rect);
|
||||||
const blob = await this.getBlob({ rect });
|
|
||||||
if (this.manager._isDebugging) {
|
if (this.manager._isDebugging) {
|
||||||
previewBlob(blob, 'Rasterized entity');
|
previewBlob(blob, 'Rasterized entity');
|
||||||
}
|
}
|
||||||
@ -408,41 +399,20 @@ export class CanvasObjectRenderer {
|
|||||||
this.manager.stateApi.rasterizeEntity({
|
this.manager.stateApi.rasterizeEntity({
|
||||||
entityIdentifier: this.parent.getEntityIdentifier(),
|
entityIdentifier: this.parent.getEntityIdentifier(),
|
||||||
imageObject,
|
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;
|
return imageDTO;
|
||||||
};
|
};
|
||||||
|
|
||||||
getBlob = ({ rect }: { rect?: Rect }): Promise<Blob> => {
|
getBlob = (rect?: Rect): Promise<Blob> => {
|
||||||
return konvaNodeToBlob(this.konva.objectGroup.clone(), rect);
|
return konvaNodeToBlob(this.konva.objectGroup.clone(), rect);
|
||||||
};
|
};
|
||||||
|
|
||||||
getImageData = ({ rect }: { rect?: Rect }): ImageData => {
|
getImageData = (rect?: Rect): ImageData => {
|
||||||
return konvaNodeToImageData(this.konva.objectGroup.clone(), rect);
|
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.
|
* Destroys this renderer and all of its object renderers.
|
||||||
*/
|
*/
|
||||||
|
@ -26,9 +26,7 @@ import {
|
|||||||
entityReset,
|
entityReset,
|
||||||
entitySelected,
|
entitySelected,
|
||||||
eraserWidthChanged,
|
eraserWidthChanged,
|
||||||
imImageCacheChanged,
|
layerCompositeRasterized,
|
||||||
layerImageCacheChanged,
|
|
||||||
rgImageCacheChanged,
|
|
||||||
toolBufferChanged,
|
toolBufferChanged,
|
||||||
toolChanged,
|
toolChanged,
|
||||||
} from 'features/controlLayers/store/canvasV2Slice';
|
} from 'features/controlLayers/store/canvasV2Slice';
|
||||||
@ -51,7 +49,6 @@ import type {
|
|||||||
import { RGBA_RED } from 'features/controlLayers/store/types';
|
import { RGBA_RED } from 'features/controlLayers/store/types';
|
||||||
import type { WritableAtom } from 'nanostores';
|
import type { WritableAtom } from 'nanostores';
|
||||||
import { atom } from 'nanostores';
|
import { atom } from 'nanostores';
|
||||||
import type { ImageDTO } from 'services/api/types';
|
|
||||||
|
|
||||||
type EntityStateAndAdapter =
|
type EntityStateAndAdapter =
|
||||||
| {
|
| {
|
||||||
@ -118,6 +115,10 @@ export class CanvasStateApi {
|
|||||||
log.trace(arg, 'Rasterizing entity');
|
log.trace(arg, 'Rasterizing entity');
|
||||||
this._store.dispatch(entityRasterized(arg));
|
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) => {
|
setSelectedEntity = (arg: EntityIdentifierPayload) => {
|
||||||
log.trace({ arg }, 'Setting selected entity');
|
log.trace({ arg }, 'Setting selected entity');
|
||||||
this._store.dispatch(entitySelected(arg));
|
this._store.dispatch(entitySelected(arg));
|
||||||
@ -134,18 +135,6 @@ export class CanvasStateApi {
|
|||||||
log.trace({ width }, 'Setting eraser width');
|
log.trace({ width }, 'Setting eraser width');
|
||||||
this._store.dispatch(eraserWidthChanged(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) => {
|
setTool = (tool: Tool) => {
|
||||||
log.trace({ tool }, 'Setting tool');
|
log.trace({ tool }, 'Setting tool');
|
||||||
this._store.dispatch(toolChanged(tool));
|
this._store.dispatch(toolChanged(tool));
|
||||||
@ -171,9 +160,6 @@ export class CanvasStateApi {
|
|||||||
getLayersState = () => {
|
getLayersState = () => {
|
||||||
return this.getState().layers;
|
return this.getState().layers;
|
||||||
};
|
};
|
||||||
getControlAdaptersState = () => {
|
|
||||||
return this.getState().controlAdapters;
|
|
||||||
};
|
|
||||||
getInpaintMaskState = () => {
|
getInpaintMaskState = () => {
|
||||||
return this.getState().inpaintMask;
|
return this.getState().inpaintMask;
|
||||||
};
|
};
|
||||||
@ -202,9 +188,6 @@ export class CanvasStateApi {
|
|||||||
if (identifier.type === 'layer') {
|
if (identifier.type === 'layer') {
|
||||||
entityState = state.layers.entities.find((i) => i.id === identifier.id) ?? null;
|
entityState = state.layers.entities.find((i) => i.id === identifier.id) ?? null;
|
||||||
entityAdapter = this.manager.layers.get(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') {
|
} else if (identifier.type === 'regional_guidance') {
|
||||||
entityState = state.regions.entities.find((i) => i.id === identifier.id) ?? null;
|
entityState = state.regions.entities.find((i) => i.id === identifier.id) ?? null;
|
||||||
entityAdapter = this.manager.regions.get(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 { deepClone } from 'common/util/deepClone';
|
||||||
import { bboxReducers } from 'features/controlLayers/store/bboxReducers';
|
import { bboxReducers } from 'features/controlLayers/store/bboxReducers';
|
||||||
import { compositingReducers } from 'features/controlLayers/store/compositingReducers';
|
import { compositingReducers } from 'features/controlLayers/store/compositingReducers';
|
||||||
import { controlAdaptersReducers } from 'features/controlLayers/store/controlAdaptersReducers';
|
|
||||||
import { inpaintMaskReducers } from 'features/controlLayers/store/inpaintMaskReducers';
|
import { inpaintMaskReducers } from 'features/controlLayers/store/inpaintMaskReducers';
|
||||||
import { ipAdaptersReducers } from 'features/controlLayers/store/ipAdaptersReducers';
|
import { ipAdaptersReducers } from 'features/controlLayers/store/ipAdaptersReducers';
|
||||||
import { layersReducers } from 'features/controlLayers/store/layersReducers';
|
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 { getScaledBoundingBoxDimensions } from 'features/controlLayers/util/getScaledBoundingBoxDimensions';
|
||||||
import { initialAspectRatioState } from 'features/parameters/components/DocumentSize/constants';
|
import { initialAspectRatioState } from 'features/parameters/components/DocumentSize/constants';
|
||||||
import { getOptimalDimension } from 'features/parameters/util/optimalDimension';
|
import { getOptimalDimension } from 'features/parameters/util/optimalDimension';
|
||||||
import { pick } from 'lodash-es';
|
import { isEqual, pick } from 'lodash-es';
|
||||||
import { atom } from 'nanostores';
|
import { atom } from 'nanostores';
|
||||||
import type { InvocationDenoiseProgressEvent } from 'services/events/types';
|
import type { InvocationDenoiseProgressEvent } from 'services/events/types';
|
||||||
import { assert } from 'tsafe';
|
import { assert } from 'tsafe';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
CanvasEntityIdentifier,
|
CanvasEntityIdentifier,
|
||||||
|
CanvasInpaintMaskState,
|
||||||
|
CanvasLayerState,
|
||||||
|
CanvasRegionalGuidanceState,
|
||||||
CanvasV2State,
|
CanvasV2State,
|
||||||
Coordinate,
|
Coordinate,
|
||||||
EntityBrushLineAddedPayload,
|
EntityBrushLineAddedPayload,
|
||||||
@ -41,8 +43,7 @@ import { IMAGE_FILTERS, RGBA_RED } from './types';
|
|||||||
const initialState: CanvasV2State = {
|
const initialState: CanvasV2State = {
|
||||||
_version: 3,
|
_version: 3,
|
||||||
selectedEntityIdentifier: null,
|
selectedEntityIdentifier: null,
|
||||||
layers: { entities: [], imageCache: null },
|
layers: { entities: [], compositeRasterizationCache: [] },
|
||||||
controlAdapters: { entities: [] },
|
|
||||||
ipAdapters: { entities: [] },
|
ipAdapters: { entities: [] },
|
||||||
regions: { entities: [] },
|
regions: { entities: [] },
|
||||||
loras: [],
|
loras: [],
|
||||||
@ -50,7 +51,7 @@ const initialState: CanvasV2State = {
|
|||||||
id: 'inpaint_mask',
|
id: 'inpaint_mask',
|
||||||
type: 'inpaint_mask',
|
type: 'inpaint_mask',
|
||||||
fill: RGBA_RED,
|
fill: RGBA_RED,
|
||||||
imageCache: null,
|
rasterizationCache: [],
|
||||||
isEnabled: true,
|
isEnabled: true,
|
||||||
objects: [],
|
objects: [],
|
||||||
position: {
|
position: {
|
||||||
@ -144,8 +145,6 @@ export function selectEntity(state: CanvasV2State, { id, type }: CanvasEntityIde
|
|||||||
switch (type) {
|
switch (type) {
|
||||||
case 'layer':
|
case 'layer':
|
||||||
return state.layers.entities.find((layer) => layer.id === id);
|
return state.layers.entities.find((layer) => layer.id === id);
|
||||||
case 'control_adapter':
|
|
||||||
return state.controlAdapters.entities.find((ca) => ca.id === id);
|
|
||||||
case 'inpaint_mask':
|
case 'inpaint_mask':
|
||||||
return state.inpaintMask;
|
return state.inpaintMask;
|
||||||
case 'regional_guidance':
|
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({
|
export const canvasV2Slice = createSlice({
|
||||||
name: 'canvasV2',
|
name: 'canvasV2',
|
||||||
initialState,
|
initialState,
|
||||||
reducers: {
|
reducers: {
|
||||||
...layersReducers,
|
...layersReducers,
|
||||||
...ipAdaptersReducers,
|
...ipAdaptersReducers,
|
||||||
...controlAdaptersReducers,
|
|
||||||
...regionsReducers,
|
...regionsReducers,
|
||||||
...lorasReducers,
|
...lorasReducers,
|
||||||
...paramsReducers,
|
...paramsReducers,
|
||||||
@ -182,16 +205,11 @@ export const canvasV2Slice = createSlice({
|
|||||||
const entity = selectEntity(state, entityIdentifier);
|
const entity = selectEntity(state, entityIdentifier);
|
||||||
if (!entity) {
|
if (!entity) {
|
||||||
return;
|
return;
|
||||||
} else if (entity.type === 'layer') {
|
} else if (entity.type === 'layer' || entity.type === 'inpaint_mask' || entity.type === 'regional_guidance') {
|
||||||
entity.isEnabled = true;
|
entity.isEnabled = true;
|
||||||
entity.objects = [];
|
entity.objects = [];
|
||||||
entity.position = { x: 0, y: 0 };
|
entity.position = { x: 0, y: 0 };
|
||||||
state.layers.imageCache = null;
|
invalidateRasterizationCaches(entity, state);
|
||||||
} else if (entity.type === 'inpaint_mask' || entity.type === 'regional_guidance') {
|
|
||||||
entity.isEnabled = true;
|
|
||||||
entity.objects = [];
|
|
||||||
entity.position = { x: 0, y: 0 };
|
|
||||||
entity.imageCache = null;
|
|
||||||
} else {
|
} else {
|
||||||
assert(false, 'Not implemented');
|
assert(false, 'Not implemented');
|
||||||
}
|
}
|
||||||
@ -209,32 +227,28 @@ export const canvasV2Slice = createSlice({
|
|||||||
const entity = selectEntity(state, entityIdentifier);
|
const entity = selectEntity(state, entityIdentifier);
|
||||||
if (!entity) {
|
if (!entity) {
|
||||||
return;
|
return;
|
||||||
} else if (entity.type === 'layer') {
|
}
|
||||||
|
|
||||||
|
if (entity.type === 'layer' || entity.type === 'inpaint_mask' || entity.type === 'regional_guidance') {
|
||||||
entity.position = position;
|
entity.position = position;
|
||||||
state.layers.imageCache = null;
|
// When an entity is moved, we need to invalidate the rasterization caches.
|
||||||
} else if (entity.type === 'inpaint_mask' || entity.type === 'regional_guidance') {
|
invalidateRasterizationCaches(entity, state);
|
||||||
entity.position = position;
|
|
||||||
entity.imageCache = null;
|
|
||||||
} else {
|
|
||||||
assert(false, 'Not implemented');
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
entityRasterized: (state, action: PayloadAction<EntityRasterizedPayload>) => {
|
entityRasterized: (state, action: PayloadAction<EntityRasterizedPayload>) => {
|
||||||
const { entityIdentifier, imageObject, position } = action.payload;
|
const { entityIdentifier, imageObject, rect } = action.payload;
|
||||||
const entity = selectEntity(state, entityIdentifier);
|
const entity = selectEntity(state, entityIdentifier);
|
||||||
if (!entity) {
|
if (!entity) {
|
||||||
return;
|
return;
|
||||||
} else if (entity.type === 'layer') {
|
}
|
||||||
|
|
||||||
|
if (entity.type === 'layer' || entity.type === 'inpaint_mask' || entity.type === 'regional_guidance') {
|
||||||
entity.objects = [imageObject];
|
entity.objects = [imageObject];
|
||||||
entity.position = position;
|
entity.position = { x: rect.x, y: rect.y };
|
||||||
entity.imageCache = imageObject.image.image_name;
|
// Remove the cache for the given rect. This should never happen, because we should never rasterize the same
|
||||||
state.layers.imageCache = null;
|
// rect twice. Just in case, we remove the old cache.
|
||||||
} else if (entity.type === 'inpaint_mask' || entity.type === 'regional_guidance') {
|
entity.rasterizationCache = entity.rasterizationCache.filter((cache) => !isEqual(cache.rect, rect));
|
||||||
entity.objects = [imageObject];
|
entity.rasterizationCache.push({ imageName: imageObject.image.image_name, rect });
|
||||||
entity.position = position;
|
|
||||||
entity.imageCache = imageObject.image.image_name;
|
|
||||||
} else {
|
|
||||||
assert(false, 'Not implemented');
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
entityBrushLineAdded: (state, action: PayloadAction<EntityBrushLineAddedPayload>) => {
|
entityBrushLineAdded: (state, action: PayloadAction<EntityBrushLineAddedPayload>) => {
|
||||||
@ -242,14 +256,12 @@ export const canvasV2Slice = createSlice({
|
|||||||
const entity = selectEntity(state, entityIdentifier);
|
const entity = selectEntity(state, entityIdentifier);
|
||||||
if (!entity) {
|
if (!entity) {
|
||||||
return;
|
return;
|
||||||
} else if (entity.type === 'layer') {
|
}
|
||||||
|
|
||||||
|
if (entity.type === 'layer' || entity.type === 'inpaint_mask' || entity.type === 'regional_guidance') {
|
||||||
entity.objects.push(brushLine);
|
entity.objects.push(brushLine);
|
||||||
state.layers.imageCache = null;
|
// When adding a brush line, we need to invalidate the rasterization caches.
|
||||||
} else if (entity.type === 'inpaint_mask' || entity.type === 'regional_guidance') {
|
invalidateRasterizationCaches(entity, state);
|
||||||
entity.objects.push(brushLine);
|
|
||||||
entity.imageCache = null;
|
|
||||||
} else {
|
|
||||||
assert(false, 'Not implemented');
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
entityEraserLineAdded: (state, action: PayloadAction<EntityEraserLineAddedPayload>) => {
|
entityEraserLineAdded: (state, action: PayloadAction<EntityEraserLineAddedPayload>) => {
|
||||||
@ -257,12 +269,10 @@ export const canvasV2Slice = createSlice({
|
|||||||
const entity = selectEntity(state, entityIdentifier);
|
const entity = selectEntity(state, entityIdentifier);
|
||||||
if (!entity) {
|
if (!entity) {
|
||||||
return;
|
return;
|
||||||
} else if (entity.type === 'layer') {
|
} else if (entity.type === 'layer' || entity.type === 'inpaint_mask' || entity.type === 'regional_guidance') {
|
||||||
entity.objects.push(eraserLine);
|
entity.objects.push(eraserLine);
|
||||||
state.layers.imageCache = null;
|
// When adding an eraser line, we need to invalidate the rasterization caches.
|
||||||
} else if (entity.type === 'inpaint_mask' || entity.type === 'regional_guidance') {
|
invalidateRasterizationCaches(entity, state);
|
||||||
entity.objects.push(eraserLine);
|
|
||||||
entity.imageCache = null;
|
|
||||||
} else {
|
} else {
|
||||||
assert(false, 'Not implemented');
|
assert(false, 'Not implemented');
|
||||||
}
|
}
|
||||||
@ -274,19 +284,21 @@ export const canvasV2Slice = createSlice({
|
|||||||
return;
|
return;
|
||||||
} else if (entity.type === 'layer') {
|
} else if (entity.type === 'layer') {
|
||||||
entity.objects.push(rect);
|
entity.objects.push(rect);
|
||||||
state.layers.imageCache = null;
|
// When adding an eraser line, we need to invalidate the rasterization caches.
|
||||||
} else if (entity.type === 'inpaint_mask' || entity.type === 'regional_guidance') {
|
invalidateRasterizationCaches(entity, state);
|
||||||
entity.objects.push(rect);
|
|
||||||
entity.imageCache = null;
|
|
||||||
} else {
|
} else {
|
||||||
assert(false, 'Not implemented');
|
assert(false, 'Not implemented');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
entityDeleted: (state, action: PayloadAction<EntityIdentifierPayload>) => {
|
entityDeleted: (state, action: PayloadAction<EntityIdentifierPayload>) => {
|
||||||
const { entityIdentifier } = action.payload;
|
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') {
|
if (entityIdentifier.type === 'layer') {
|
||||||
state.layers.entities = state.layers.entities.filter((layer) => layer.id !== entityIdentifier.id);
|
state.layers.entities = state.layers.entities.filter((layer) => layer.id !== entityIdentifier.id);
|
||||||
state.layers.imageCache = null;
|
|
||||||
} else if (entityIdentifier.type === 'regional_guidance') {
|
} else if (entityIdentifier.type === 'regional_guidance') {
|
||||||
state.regions.entities = state.regions.entities.filter((rg) => rg.id !== entityIdentifier.id);
|
state.regions.entities = state.regions.entities.filter((rg) => rg.id !== entityIdentifier.id);
|
||||||
} else {
|
} else {
|
||||||
@ -301,11 +313,10 @@ export const canvasV2Slice = createSlice({
|
|||||||
}
|
}
|
||||||
if (entity.type === 'layer') {
|
if (entity.type === 'layer') {
|
||||||
moveOneToEnd(state.layers.entities, entity);
|
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') {
|
} else if (entity.type === 'regional_guidance') {
|
||||||
moveOneToEnd(state.regions.entities, entity);
|
moveOneToEnd(state.regions.entities, entity);
|
||||||
} else if (entity.type === 'control_adapter') {
|
|
||||||
moveOneToEnd(state.controlAdapters.entities, entity);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
entityArrangedToFront: (state, action: PayloadAction<EntityIdentifierPayload>) => {
|
entityArrangedToFront: (state, action: PayloadAction<EntityIdentifierPayload>) => {
|
||||||
@ -316,11 +327,10 @@ export const canvasV2Slice = createSlice({
|
|||||||
}
|
}
|
||||||
if (entity.type === 'layer') {
|
if (entity.type === 'layer') {
|
||||||
moveToEnd(state.layers.entities, entity);
|
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') {
|
} else if (entity.type === 'regional_guidance') {
|
||||||
moveToEnd(state.regions.entities, entity);
|
moveToEnd(state.regions.entities, entity);
|
||||||
} else if (entity.type === 'control_adapter') {
|
|
||||||
moveToEnd(state.controlAdapters.entities, entity);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
entityArrangedBackwardOne: (state, action: PayloadAction<EntityIdentifierPayload>) => {
|
entityArrangedBackwardOne: (state, action: PayloadAction<EntityIdentifierPayload>) => {
|
||||||
@ -331,11 +341,10 @@ export const canvasV2Slice = createSlice({
|
|||||||
}
|
}
|
||||||
if (entity.type === 'layer') {
|
if (entity.type === 'layer') {
|
||||||
moveOneToStart(state.layers.entities, entity);
|
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') {
|
} else if (entity.type === 'regional_guidance') {
|
||||||
moveOneToStart(state.regions.entities, entity);
|
moveOneToStart(state.regions.entities, entity);
|
||||||
} else if (entity.type === 'control_adapter') {
|
|
||||||
moveOneToStart(state.controlAdapters.entities, entity);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
entityArrangedToBack: (state, action: PayloadAction<EntityIdentifierPayload>) => {
|
entityArrangedToBack: (state, action: PayloadAction<EntityIdentifierPayload>) => {
|
||||||
@ -346,19 +355,17 @@ export const canvasV2Slice = createSlice({
|
|||||||
}
|
}
|
||||||
if (entity.type === 'layer') {
|
if (entity.type === 'layer') {
|
||||||
moveToStart(state.layers.entities, entity);
|
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') {
|
} else if (entity.type === 'regional_guidance') {
|
||||||
moveToStart(state.regions.entities, entity);
|
moveToStart(state.regions.entities, entity);
|
||||||
} else if (entity.type === 'control_adapter') {
|
|
||||||
moveToStart(state.controlAdapters.entities, entity);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
allEntitiesDeleted: (state) => {
|
allEntitiesDeleted: (state) => {
|
||||||
state.regions.entities = [];
|
state.regions.entities = [];
|
||||||
state.layers.entities = [];
|
state.layers.entities = [];
|
||||||
state.layers.imageCache = null;
|
state.layers.compositeRasterizationCache = [];
|
||||||
state.ipAdapters.entities = [];
|
state.ipAdapters.entities = [];
|
||||||
state.controlAdapters.entities = [];
|
|
||||||
},
|
},
|
||||||
filterSelected: (state, action: PayloadAction<{ type: FilterConfig['type'] }>) => {
|
filterSelected: (state, action: PayloadAction<{ type: FilterConfig['type'] }>) => {
|
||||||
state.filter.config = IMAGE_FILTERS[action.payload.type].buildDefaults();
|
state.filter.config = IMAGE_FILTERS[action.payload.type].buildDefaults();
|
||||||
@ -366,6 +373,23 @@ export const canvasV2Slice = createSlice({
|
|||||||
filterConfigChanged: (state, action: PayloadAction<{ config: FilterConfig }>) => {
|
filterConfigChanged: (state, action: PayloadAction<{ config: FilterConfig }>) => {
|
||||||
state.filter.config = action.payload.config;
|
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) => {
|
canvasReset: (state) => {
|
||||||
state.bbox = deepClone(initialState.bbox);
|
state.bbox = deepClone(initialState.bbox);
|
||||||
const optimalDimension = getOptimalDimension(state.params.model);
|
const optimalDimension = getOptimalDimension(state.params.model);
|
||||||
@ -374,7 +398,6 @@ export const canvasV2Slice = createSlice({
|
|||||||
const size = pick(state.bbox.rect, 'width', 'height');
|
const size = pick(state.bbox.rect, 'width', 'height');
|
||||||
state.bbox.scaledSize = getScaledBoundingBoxDimensions(size, optimalDimension);
|
state.bbox.scaledSize = getScaledBoundingBoxDimensions(size, optimalDimension);
|
||||||
|
|
||||||
state.controlAdapters = deepClone(initialState.controlAdapters);
|
|
||||||
state.ipAdapters = deepClone(initialState.ipAdapters);
|
state.ipAdapters = deepClone(initialState.ipAdapters);
|
||||||
state.layers = deepClone(initialState.layers);
|
state.layers = deepClone(initialState.layers);
|
||||||
state.regions = deepClone(initialState.regions);
|
state.regions = deepClone(initialState.regions);
|
||||||
@ -397,6 +420,7 @@ export const {
|
|||||||
allEntitiesDeleted,
|
allEntitiesDeleted,
|
||||||
clipToBboxChanged,
|
clipToBboxChanged,
|
||||||
canvasReset,
|
canvasReset,
|
||||||
|
rasterizationCachesInvalidated,
|
||||||
// All entities
|
// All entities
|
||||||
entitySelected,
|
entitySelected,
|
||||||
entityReset,
|
entityReset,
|
||||||
@ -424,14 +448,13 @@ export const {
|
|||||||
// layers
|
// layers
|
||||||
layerAdded,
|
layerAdded,
|
||||||
layerRecalled,
|
layerRecalled,
|
||||||
layerOpacityChanged,
|
|
||||||
layerAllDeleted,
|
layerAllDeleted,
|
||||||
layerImageCacheChanged,
|
|
||||||
layerUsedAsControlChanged,
|
layerUsedAsControlChanged,
|
||||||
layerControlAdapterModelChanged,
|
layerControlAdapterModelChanged,
|
||||||
layerControlAdapterControlModeChanged,
|
layerControlAdapterControlModeChanged,
|
||||||
layerControlAdapterWeightChanged,
|
layerControlAdapterWeightChanged,
|
||||||
layerControlAdapterBeginEndStepPctChanged,
|
layerControlAdapterBeginEndStepPctChanged,
|
||||||
|
layerCompositeRasterized,
|
||||||
// IP Adapters
|
// IP Adapters
|
||||||
ipaAdded,
|
ipaAdded,
|
||||||
ipaRecalled,
|
ipaRecalled,
|
||||||
@ -444,20 +467,6 @@ export const {
|
|||||||
ipaCLIPVisionModelChanged,
|
ipaCLIPVisionModelChanged,
|
||||||
ipaWeightChanged,
|
ipaWeightChanged,
|
||||||
ipaBeginEndStepPctChanged,
|
ipaBeginEndStepPctChanged,
|
||||||
// Control Adapters
|
|
||||||
caAdded,
|
|
||||||
caAllDeleted,
|
|
||||||
caOpacityChanged,
|
|
||||||
caRecalled,
|
|
||||||
caImageChanged,
|
|
||||||
caProcessedImageChanged,
|
|
||||||
caModelChanged,
|
|
||||||
caControlModeChanged,
|
|
||||||
caProcessorConfigChanged,
|
|
||||||
caFilterChanged,
|
|
||||||
caProcessorPendingBatchIdChanged,
|
|
||||||
caWeightChanged,
|
|
||||||
caBeginEndStepPctChanged,
|
|
||||||
// Regions
|
// Regions
|
||||||
rgAdded,
|
rgAdded,
|
||||||
rgRecalled,
|
rgRecalled,
|
||||||
@ -465,7 +474,6 @@ export const {
|
|||||||
rgPositivePromptChanged,
|
rgPositivePromptChanged,
|
||||||
rgNegativePromptChanged,
|
rgNegativePromptChanged,
|
||||||
rgFillChanged,
|
rgFillChanged,
|
||||||
rgImageCacheChanged,
|
|
||||||
rgAutoNegativeChanged,
|
rgAutoNegativeChanged,
|
||||||
rgIPAdapterAdded,
|
rgIPAdapterAdded,
|
||||||
rgIPAdapterDeleted,
|
rgIPAdapterDeleted,
|
||||||
@ -522,7 +530,6 @@ export const {
|
|||||||
// Inpaint mask
|
// Inpaint mask
|
||||||
imRecalled,
|
imRecalled,
|
||||||
imFillChanged,
|
imFillChanged,
|
||||||
imImageCacheChanged,
|
|
||||||
// Staging
|
// Staging
|
||||||
sessionStartedStaging,
|
sessionStartedStaging,
|
||||||
sessionImageStaged,
|
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 { PayloadAction, SliceCaseReducers } from '@reduxjs/toolkit';
|
||||||
import type { CanvasInpaintMaskState, CanvasV2State } from 'features/controlLayers/store/types';
|
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';
|
import type { RgbColor } from './types';
|
||||||
|
|
||||||
@ -15,8 +13,4 @@ export const inpaintMaskReducers = {
|
|||||||
const { fill } = action.payload;
|
const { fill } = action.payload;
|
||||||
state.inpaintMask.fill = fill;
|
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>;
|
} satisfies SliceCaseReducers<CanvasV2State>;
|
||||||
|
@ -1,12 +1,11 @@
|
|||||||
import type { PayloadAction, SliceCaseReducers } from '@reduxjs/toolkit';
|
import type { PayloadAction, SliceCaseReducers } from '@reduxjs/toolkit';
|
||||||
import { getPrefixedId } from 'features/controlLayers/konva/util';
|
import { getPrefixedId } from 'features/controlLayers/konva/util';
|
||||||
import { zModelIdentifierField } from 'features/nodes/types/common';
|
import { zModelIdentifierField } from 'features/nodes/types/common';
|
||||||
import { merge } from 'lodash-es';
|
import { isEqual, merge } from 'lodash-es';
|
||||||
import type { ControlNetModelConfig, ImageDTO, T2IAdapterModelConfig } from 'services/api/types';
|
import type { ControlNetModelConfig, T2IAdapterModelConfig } from 'services/api/types';
|
||||||
import { assert } from 'tsafe';
|
import { assert } from 'tsafe';
|
||||||
|
|
||||||
import type { CanvasLayerState, CanvasV2State, ControlModeV2, ControlNetConfig, T2IAdapterConfig } from './types';
|
import type { CanvasLayerState, CanvasV2State, ControlModeV2, ControlNetConfig, Rect, T2IAdapterConfig } from './types';
|
||||||
import { imageDTOToImageWithDims } from './types';
|
|
||||||
|
|
||||||
export const selectLayer = (state: CanvasV2State, id: string) => state.layers.entities.find((layer) => layer.id === id);
|
export const selectLayer = (state: CanvasV2State, id: string) => state.layers.entities.find((layer) => layer.id === id);
|
||||||
export const selectLayerOrThrow = (state: CanvasV2State, id: string) => {
|
export const selectLayerOrThrow = (state: CanvasV2State, id: string) => {
|
||||||
@ -29,7 +28,7 @@ export const layersReducers = {
|
|||||||
objects: [],
|
objects: [],
|
||||||
opacity: 1,
|
opacity: 1,
|
||||||
position: { x: 0, y: 0 },
|
position: { x: 0, y: 0 },
|
||||||
imageCache: null,
|
rasterizationCache: [],
|
||||||
controlAdapter: null,
|
controlAdapter: null,
|
||||||
};
|
};
|
||||||
merge(layer, overrides);
|
merge(layer, overrides);
|
||||||
@ -37,7 +36,11 @@ export const layersReducers = {
|
|||||||
if (isSelected) {
|
if (isSelected) {
|
||||||
state.selectedEntityIdentifier = { type: 'layer', id };
|
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 }) => ({
|
prepare: (payload: { overrides?: Partial<CanvasLayerState>; isSelected?: boolean }) => ({
|
||||||
payload: { ...payload, id: getPrefixedId('layer') },
|
payload: { ...payload, id: getPrefixedId('layer') },
|
||||||
@ -47,24 +50,20 @@ export const layersReducers = {
|
|||||||
const { data } = action.payload;
|
const { data } = action.payload;
|
||||||
state.layers.entities.push(data);
|
state.layers.entities.push(data);
|
||||||
state.selectedEntityIdentifier = { type: 'layer', id: data.id };
|
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) => {
|
layerAllDeleted: (state) => {
|
||||||
state.layers.entities = [];
|
state.layers.entities = [];
|
||||||
state.layers.imageCache = null;
|
state.layers.compositeRasterizationCache = [];
|
||||||
},
|
},
|
||||||
layerOpacityChanged: (state, action: PayloadAction<{ id: string; opacity: number }>) => {
|
layerCompositeRasterized: (state, action: PayloadAction<{ imageName: string; rect: Rect }>) => {
|
||||||
const { id, opacity } = action.payload;
|
state.layers.compositeRasterizationCache = state.layers.compositeRasterizationCache.filter(
|
||||||
const layer = selectLayer(state, id);
|
(cache) => !isEqual(cache.rect, action.payload.rect)
|
||||||
if (!layer) {
|
);
|
||||||
return;
|
state.layers.compositeRasterizationCache.push(action.payload);
|
||||||
}
|
|
||||||
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;
|
|
||||||
},
|
},
|
||||||
layerUsedAsControlChanged: (
|
layerUsedAsControlChanged: (
|
||||||
state,
|
state,
|
||||||
@ -76,6 +75,8 @@ export const layersReducers = {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
layer.controlAdapter = controlAdapter;
|
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: (
|
layerControlAdapterModelChanged: (
|
||||||
state,
|
state,
|
||||||
|
@ -54,7 +54,7 @@ export const regionsReducers = {
|
|||||||
positivePrompt: '',
|
positivePrompt: '',
|
||||||
negativePrompt: null,
|
negativePrompt: null,
|
||||||
ipAdapters: [],
|
ipAdapters: [],
|
||||||
imageCache: null,
|
rasterizationCache: [],
|
||||||
};
|
};
|
||||||
state.regions.entities.push(rg);
|
state.regions.entities.push(rg);
|
||||||
state.selectedEntityIdentifier = { type: 'regional_guidance', id };
|
state.selectedEntityIdentifier = { type: 'regional_guidance', id };
|
||||||
@ -93,14 +93,6 @@ export const regionsReducers = {
|
|||||||
}
|
}
|
||||||
rg.fill = fill;
|
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 }>) => {
|
rgAutoNegativeChanged: (state, action: PayloadAction<{ id: string; autoNegative: ParameterAutoNegative }>) => {
|
||||||
const { id, autoNegative } = action.payload;
|
const { id, autoNegative } = action.payload;
|
||||||
const rg = selectRG(state, id);
|
const rg = selectRG(state, id);
|
||||||
|
@ -5,7 +5,7 @@ import { getOptimalDimension } from 'features/parameters/util/optimalDimension';
|
|||||||
export const selectEntityCount = createSelector(selectCanvasV2Slice, (canvasV2) => {
|
export const selectEntityCount = createSelector(selectCanvasV2Slice, (canvasV2) => {
|
||||||
return (
|
return (
|
||||||
canvasV2.regions.entities.length +
|
canvasV2.regions.entities.length +
|
||||||
canvasV2.controlAdapters.entities.length +
|
// canvasV2.controlAdapters.entities.length +
|
||||||
canvasV2.ipAdapters.entities.length +
|
canvasV2.ipAdapters.entities.length +
|
||||||
canvasV2.layers.entities.length
|
canvasV2.layers.entities.length
|
||||||
);
|
);
|
||||||
|
@ -639,6 +639,12 @@ const zMaskObject = z
|
|||||||
})
|
})
|
||||||
.pipe(z.discriminatedUnion('type', [zCanvasBrushLineState, zCanvasEraserLineState, zCanvasRectState]));
|
.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({
|
export const zCanvasRegionalGuidanceState = z.object({
|
||||||
id: zId,
|
id: zId,
|
||||||
type: z.literal('regional_guidance'),
|
type: z.literal('regional_guidance'),
|
||||||
@ -650,7 +656,7 @@ export const zCanvasRegionalGuidanceState = z.object({
|
|||||||
negativePrompt: zParameterNegativePrompt.nullable(),
|
negativePrompt: zParameterNegativePrompt.nullable(),
|
||||||
ipAdapters: z.array(zCanvasIPAdapterState),
|
ipAdapters: z.array(zCanvasIPAdapterState),
|
||||||
autoNegative: zAutoNegative,
|
autoNegative: zAutoNegative,
|
||||||
imageCache: z.string().min(1).nullable(),
|
rasterizationCache: z.array(zImageCache),
|
||||||
});
|
});
|
||||||
export type CanvasRegionalGuidanceState = z.infer<typeof zCanvasRegionalGuidanceState>;
|
export type CanvasRegionalGuidanceState = z.infer<typeof zCanvasRegionalGuidanceState>;
|
||||||
|
|
||||||
@ -670,7 +676,7 @@ const zCanvasInpaintMaskState = z.object({
|
|||||||
position: zCoordinate,
|
position: zCoordinate,
|
||||||
fill: zRgbColor,
|
fill: zRgbColor,
|
||||||
objects: z.array(zCanvasObjectState),
|
objects: z.array(zCanvasObjectState),
|
||||||
imageCache: z.string().min(1).nullable(),
|
rasterizationCache: z.array(zImageCache),
|
||||||
});
|
});
|
||||||
export type CanvasInpaintMaskState = z.infer<typeof zCanvasInpaintMaskState>;
|
export type CanvasInpaintMaskState = z.infer<typeof zCanvasInpaintMaskState>;
|
||||||
|
|
||||||
@ -729,7 +735,7 @@ export const zCanvasLayerState = z.object({
|
|||||||
position: zCoordinate,
|
position: zCoordinate,
|
||||||
opacity: zOpacity,
|
opacity: zOpacity,
|
||||||
objects: z.array(zCanvasObjectState),
|
objects: z.array(zCanvasObjectState),
|
||||||
imageCache: z.string().min(1).nullable(),
|
rasterizationCache: z.array(zImageCache),
|
||||||
controlAdapter: z.discriminatedUnion('type', [zControlNetConfig, zT2IAdapterConfig]).nullable(),
|
controlAdapter: z.discriminatedUnion('type', [zControlNetConfig, zT2IAdapterConfig]).nullable(),
|
||||||
});
|
});
|
||||||
export type CanvasLayerState = z.infer<typeof zCanvasLayerState>;
|
export type CanvasLayerState = z.infer<typeof zCanvasLayerState>;
|
||||||
@ -826,11 +832,7 @@ export type CanvasV2State = {
|
|||||||
_version: 3;
|
_version: 3;
|
||||||
selectedEntityIdentifier: CanvasEntityIdentifier | null;
|
selectedEntityIdentifier: CanvasEntityIdentifier | null;
|
||||||
inpaintMask: CanvasInpaintMaskState;
|
inpaintMask: CanvasInpaintMaskState;
|
||||||
layers: {
|
layers: { entities: CanvasLayerState[]; compositeRasterizationCache: ImageCache[] };
|
||||||
imageCache: ImageWithDims | null;
|
|
||||||
entities: CanvasLayerState[];
|
|
||||||
};
|
|
||||||
controlAdapters: { entities: CanvasControlAdapterState[] };
|
|
||||||
ipAdapters: { entities: CanvasIPAdapterState[] };
|
ipAdapters: { entities: CanvasIPAdapterState[] };
|
||||||
regions: { entities: CanvasRegionalGuidanceState[] };
|
regions: { entities: CanvasRegionalGuidanceState[] };
|
||||||
loras: LoRA[];
|
loras: LoRA[];
|
||||||
@ -938,7 +940,7 @@ export type EntityRectAddedPayload = { entityIdentifier: CanvasEntityIdentifier;
|
|||||||
export type EntityRasterizedPayload = {
|
export type EntityRasterizedPayload = {
|
||||||
entityIdentifier: CanvasEntityIdentifier;
|
entityIdentifier: CanvasEntityIdentifier;
|
||||||
imageObject: CanvasImageState;
|
imageObject: CanvasImageState;
|
||||||
position: Coordinate;
|
rect: Rect;
|
||||||
};
|
};
|
||||||
export type ImageObjectAddedArg = { id: string; imageDTO: ImageDTO; position?: Coordinate };
|
export type ImageObjectAddedArg = { id: string; imageDTO: ImageDTO; position?: Coordinate };
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
bboxHeightChanged,
|
bboxHeightChanged,
|
||||||
bboxWidthChanged,
|
bboxWidthChanged,
|
||||||
caRecalled,
|
// caRecalled,
|
||||||
ipaRecalled,
|
ipaRecalled,
|
||||||
layerAllDeleted,
|
layerAllDeleted,
|
||||||
layerRecalled,
|
layerRecalled,
|
||||||
@ -43,8 +43,8 @@ import type {
|
|||||||
CanvasControlAdapterState,
|
CanvasControlAdapterState,
|
||||||
CanvasIPAdapterState,
|
CanvasIPAdapterState,
|
||||||
CanvasLayerState,
|
CanvasLayerState,
|
||||||
LoRA,
|
|
||||||
CanvasRegionalGuidanceState,
|
CanvasRegionalGuidanceState,
|
||||||
|
LoRA,
|
||||||
} from 'features/controlLayers/store/types';
|
} from 'features/controlLayers/store/types';
|
||||||
import { setHrfEnabled, setHrfMethod, setHrfStrength } from 'features/hrf/store/hrfSlice';
|
import { setHrfEnabled, setHrfMethod, setHrfStrength } from 'features/hrf/store/hrfSlice';
|
||||||
import type {
|
import type {
|
||||||
@ -271,7 +271,7 @@ const recallCA: MetadataRecallFunc<CanvasControlAdapterState> = async (ca) => {
|
|||||||
}
|
}
|
||||||
// No clobber
|
// No clobber
|
||||||
clone.id = getCAId(uuidv4());
|
clone.id = getCAId(uuidv4());
|
||||||
dispatch(caRecalled({ data: clone }));
|
// dispatch(caRecalled({ data: clone }));
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -26,10 +26,11 @@ export const addControlAdapters = async (
|
|||||||
const layersWithValidControlAdapters = layers
|
const layersWithValidControlAdapters = layers
|
||||||
.filter((layer) => layer.isEnabled)
|
.filter((layer) => layer.isEnabled)
|
||||||
.filter((layer) => doesLayerHaveValidControlAdapter(layer, base));
|
.filter((layer) => doesLayerHaveValidControlAdapter(layer, base));
|
||||||
|
|
||||||
for (const layer of layersWithValidControlAdapters) {
|
for (const layer of layersWithValidControlAdapters) {
|
||||||
const adapter = manager.layers.get(layer.id);
|
const adapter = manager.layers.get(layer.id);
|
||||||
assert(adapter, 'Adapter not found');
|
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') {
|
if (layer.controlAdapter.type === 'controlnet') {
|
||||||
await addControlNetToGraph(g, layer, imageDTO, denoise);
|
await addControlNetToGraph(g, layer, imageDTO, denoise);
|
||||||
} else {
|
} else {
|
||||||
|
@ -22,7 +22,7 @@ export const addInpaint = async (
|
|||||||
denoise.denoising_start = denoising_start;
|
denoise.denoising_start = denoising_start;
|
||||||
|
|
||||||
const initialImage = await manager.getCompositeLayerImageDTO(bbox.rect);
|
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)) {
|
if (!isEqual(scaledSize, originalSize)) {
|
||||||
// Scale before processing requires some resizing
|
// Scale before processing requires some resizing
|
||||||
|
@ -23,7 +23,7 @@ export const addOutpaint = async (
|
|||||||
denoise.denoising_start = denoising_start;
|
denoise.denoising_start = denoising_start;
|
||||||
|
|
||||||
const initialImage = await manager.getCompositeLayerImageDTO(bbox.rect);
|
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);
|
const infill = getInfill(g, compositing);
|
||||||
|
|
||||||
if (!isEqual(scaledSize, originalSize)) {
|
if (!isEqual(scaledSize, originalSize)) {
|
||||||
|
@ -43,15 +43,16 @@ export const addRegions = async (
|
|||||||
const validRegions = regions.filter((rg) => isValidRegion(rg, base));
|
const validRegions = regions.filter((rg) => isValidRegion(rg, base));
|
||||||
|
|
||||||
for (const region of validRegions) {
|
for (const region of validRegions) {
|
||||||
// Upload the mask image, or get the cached image if it exists
|
const adapter = manager.regions.get(region.id);
|
||||||
const { image_name } = await manager.getRegionMaskImageDTO(region.id, bbox);
|
assert(adapter, 'Adapter not found');
|
||||||
|
const imageDTO = await adapter.renderer.rasterize(bbox);
|
||||||
|
|
||||||
// The main mask-to-tensor node
|
// The main mask-to-tensor node
|
||||||
const maskToTensor = g.addNode({
|
const maskToTensor = g.addNode({
|
||||||
id: `${PROMPT_REGION_MASK_TO_TENSOR_PREFIX}_${region.id}`,
|
id: `${PROMPT_REGION_MASK_TO_TENSOR_PREFIX}_${region.id}`,
|
||||||
type: 'alpha_mask_to_tensor',
|
type: 'alpha_mask_to_tensor',
|
||||||
image: {
|
image: {
|
||||||
image_name,
|
image_name: imageDTO.image_name,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user