mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
tidy(ui): remove unused code, initial image
This commit is contained in:
parent
a2f91b1055
commit
da3888ba9e
@ -9,7 +9,6 @@ import { addBatchEnqueuedListener } from 'app/store/middleware/listenerMiddlewar
|
||||
import { addDeleteBoardAndImagesFulfilledListener } from 'app/store/middleware/listenerMiddleware/listeners/boardAndImagesDeleted';
|
||||
import { addBoardIdSelectedListener } from 'app/store/middleware/listenerMiddleware/listeners/boardIdSelected';
|
||||
import { addBulkDownloadListeners } from 'app/store/middleware/listenerMiddleware/listeners/bulkDownload';
|
||||
import { addCanvasSessionRequestedListener } from 'app/store/middleware/listenerMiddleware/listeners/canvasSessionRequested';
|
||||
import { addControlAdapterPreprocessor } from 'app/store/middleware/listenerMiddleware/listeners/controlAdapterPreprocessor';
|
||||
import { addEnqueueRequestedLinear } from 'app/store/middleware/listenerMiddleware/listeners/enqueueRequestedLinear';
|
||||
import { addEnqueueRequestedNodes } from 'app/store/middleware/listenerMiddleware/listeners/enqueueRequestedNodes';
|
||||
@ -90,7 +89,6 @@ addBatchEnqueuedListener(startAppListening);
|
||||
// addStagingAreaImageSavedListener(startAppListening);
|
||||
// addCommitStagingAreaImageListener(startAppListening);
|
||||
addStagingListeners(startAppListening);
|
||||
addCanvasSessionRequestedListener(startAppListening);
|
||||
|
||||
// Socket.IO
|
||||
addGeneratorProgressEventListener(startAppListening);
|
||||
|
@ -1,29 +0,0 @@
|
||||
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
||||
import {
|
||||
layerAdded,
|
||||
layerImageAdded,
|
||||
sessionRequested,
|
||||
sessionStarted,
|
||||
} from 'features/controlLayers/store/canvasV2Slice';
|
||||
import { getImageDTO } from 'services/api/endpoints/images';
|
||||
import { assert } from 'tsafe';
|
||||
|
||||
export const addCanvasSessionRequestedListener = (startAppListening: AppStartListening) => {
|
||||
startAppListening({
|
||||
actionCreator: sessionRequested,
|
||||
effect: async (action, { getState, dispatch }) => {
|
||||
const initialImageObject = getState().canvasV2.initialImage.imageObject;
|
||||
if (initialImageObject) {
|
||||
// We have an initial image that needs to be converted to a layer
|
||||
dispatch(layerAdded());
|
||||
const newLayer = getState().canvasV2.layers.entities[0];
|
||||
assert(newLayer, 'Expected new layer to be created');
|
||||
const imageDTO = await getImageDTO(initialImageObject.image.name);
|
||||
assert(imageDTO, 'Unable to fetch initial image DTO');
|
||||
dispatch(layerImageAdded({ id: newLayer.id, imageDTO }));
|
||||
}
|
||||
|
||||
dispatch(sessionStarted());
|
||||
},
|
||||
});
|
||||
};
|
@ -1,9 +1,7 @@
|
||||
/* eslint-disable i18next/no-literal-string */
|
||||
import { Flex } from '@invoke-ai/ui-library';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import ScrollableContent from 'common/components/OverlayScrollbars/ScrollableContent';
|
||||
import { CAEntityList } from 'features/controlLayers/components/ControlAdapter/CAEntityList';
|
||||
import { InitialImage } from 'features/controlLayers/components/InitialImage/InitialImage';
|
||||
import { IM } from 'features/controlLayers/components/InpaintMask/IM';
|
||||
import { IPAEntityList } from 'features/controlLayers/components/IPAdapter/IPAEntityList';
|
||||
import { LayerEntityList } from 'features/controlLayers/components/Layer/LayerEntityList';
|
||||
@ -11,17 +9,14 @@ import { RGEntityList } from 'features/controlLayers/components/RegionalGuidance
|
||||
import { memo } from 'react';
|
||||
|
||||
export const CanvasEntityList = memo(() => {
|
||||
const isCanvasSessionActive = useAppSelector((s) => s.canvasV2.session.isActive);
|
||||
|
||||
return (
|
||||
<ScrollableContent>
|
||||
<Flex flexDir="column" gap={2} data-testid="control-layers-layer-list">
|
||||
{isCanvasSessionActive && <IM />}
|
||||
<IM />
|
||||
<RGEntityList />
|
||||
<CAEntityList />
|
||||
<IPAEntityList />
|
||||
<LayerEntityList />
|
||||
{!isCanvasSessionActive && <InitialImage />}
|
||||
</Flex>
|
||||
</ScrollableContent>
|
||||
);
|
||||
|
@ -7,7 +7,6 @@ import { BrushWidth } from 'features/controlLayers/components/BrushWidth';
|
||||
import ControlLayersSettingsPopover from 'features/controlLayers/components/ControlLayersSettingsPopover';
|
||||
import { EraserWidth } from 'features/controlLayers/components/EraserWidth';
|
||||
import { FillColorPicker } from 'features/controlLayers/components/FillColorPicker';
|
||||
import { NewSessionButton } from 'features/controlLayers/components/NewSessionButton';
|
||||
import { ResetCanvasButton } from 'features/controlLayers/components/ResetCanvasButton';
|
||||
import { ToolChooser } from 'features/controlLayers/components/ToolChooser';
|
||||
import { UndoRedoButtonGroup } from 'features/controlLayers/components/UndoRedoButtonGroup';
|
||||
@ -25,7 +24,7 @@ export const ControlLayersToolbar = memo(() => {
|
||||
return;
|
||||
}
|
||||
for (const l of canvasManager.layers.values()) {
|
||||
l.calculateBbox();
|
||||
l.transformer.requestRectCalculation();
|
||||
}
|
||||
}, [canvasManager]);
|
||||
const onChangeDebugging = useCallback(
|
||||
@ -61,7 +60,6 @@ export const ControlLayersToolbar = memo(() => {
|
||||
<UndoRedoButtonGroup />
|
||||
<ControlLayersSettingsPopover />
|
||||
<ResetCanvasButton />
|
||||
<NewSessionButton />
|
||||
<ViewerToggleMenu />
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
@ -1,25 +0,0 @@
|
||||
import { useDisclosure } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { CanvasEntityContainer } from 'features/controlLayers/components/common/CanvasEntityContainer';
|
||||
import { InitialImageHeader } from 'features/controlLayers/components/InitialImage/InitialImageHeader';
|
||||
import { InitialImageSettings } from 'features/controlLayers/components/InitialImage/InitialImageSettings';
|
||||
import { entitySelected } from 'features/controlLayers/store/canvasV2Slice';
|
||||
import { memo, useCallback } from 'react';
|
||||
|
||||
export const InitialImage = memo(() => {
|
||||
const dispatch = useAppDispatch();
|
||||
const isSelected = useAppSelector((s) => s.canvasV2.selectedEntityIdentifier?.id === 'initial_image');
|
||||
const { isOpen, onToggle } = useDisclosure({ defaultIsOpen: true });
|
||||
const onSelect = useCallback(() => {
|
||||
dispatch(entitySelected({ id: 'initial_image', type: 'initial_image' }));
|
||||
}, [dispatch]);
|
||||
|
||||
return (
|
||||
<CanvasEntityContainer isSelected={isSelected} onSelect={onSelect}>
|
||||
<InitialImageHeader onToggleVisibility={onToggle} />
|
||||
{isOpen && <InitialImageSettings />}
|
||||
</CanvasEntityContainer>
|
||||
);
|
||||
});
|
||||
|
||||
InitialImage.displayName = 'InitialImage';
|
@ -1,34 +0,0 @@
|
||||
import { Spacer } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
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 { iiIsEnabledToggled } from 'features/controlLayers/store/canvasV2Slice';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
type Props = {
|
||||
onToggleVisibility: () => void;
|
||||
};
|
||||
|
||||
export const InitialImageHeader = memo(({ onToggleVisibility }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
const isEnabled = useAppSelector((s) => s.canvasV2.initialImage.isEnabled);
|
||||
const onToggleIsEnabled = useCallback(() => {
|
||||
dispatch(iiIsEnabledToggled());
|
||||
}, [dispatch]);
|
||||
const title = useMemo(() => {
|
||||
return `${t('controlLayers.initialImage')}`;
|
||||
}, [t]);
|
||||
|
||||
return (
|
||||
<CanvasEntityHeader onToggle={onToggleVisibility}>
|
||||
<CanvasEntityEnabledToggle isEnabled={isEnabled} onToggle={onToggleIsEnabled} />
|
||||
<CanvasEntityTitle title={title} />
|
||||
<Spacer />
|
||||
</CanvasEntityHeader>
|
||||
);
|
||||
});
|
||||
|
||||
InitialImageHeader.displayName = 'InitialImageHeader';
|
@ -1,100 +0,0 @@
|
||||
import { Flex, 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, iiReset } from 'features/controlLayers/store/canvasV2Slice';
|
||||
import { selectOptimalDimension } from 'features/controlLayers/store/selectors';
|
||||
import type { ImageDraggableData, InitialImageDropData } from 'features/dnd/types';
|
||||
import { calculateNewSize } from 'features/parameters/components/DocumentSize/calculateNewSize';
|
||||
import { memo, useCallback, useEffect, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PiArrowCounterClockwiseBold, PiRulerBold } from 'react-icons/pi';
|
||||
import { useGetImageDTOQuery } from 'services/api/endpoints/images';
|
||||
|
||||
export const InitialImagePreview = memo(() => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
const initialImage = useAppSelector((s) => s.canvasV2.initialImage);
|
||||
const isConnected = useAppSelector((s) => s.system.isConnected);
|
||||
const optimalDimension = useAppSelector(selectOptimalDimension);
|
||||
const shift = useShiftModifier();
|
||||
|
||||
const { currentData: imageDTO, isError: isErrorControlImage } = useGetImageDTOQuery(
|
||||
initialImage.imageObject?.image.name ?? skipToken
|
||||
);
|
||||
|
||||
const onReset = useCallback(() => {
|
||||
dispatch(iiReset());
|
||||
}, [dispatch]);
|
||||
|
||||
const onUseSize = useCallback(() => {
|
||||
if (!imageDTO) {
|
||||
return;
|
||||
}
|
||||
|
||||
const options = { updateAspectRatio: true, clamp: true };
|
||||
if (shift) {
|
||||
const { width, height } = imageDTO;
|
||||
dispatch(bboxWidthChanged({ width, ...options }));
|
||||
dispatch(bboxHeightChanged({ height, ...options }));
|
||||
} else {
|
||||
const { width, height } = calculateNewSize(imageDTO.width / imageDTO.height, optimalDimension * optimalDimension);
|
||||
dispatch(bboxWidthChanged({ width, ...options }));
|
||||
dispatch(bboxHeightChanged({ height, ...options }));
|
||||
}
|
||||
}, [imageDTO, dispatch, optimalDimension, shift]);
|
||||
|
||||
const draggableData = useMemo<ImageDraggableData | undefined>(() => {
|
||||
if (imageDTO) {
|
||||
return {
|
||||
id: 'initial_image',
|
||||
payloadType: 'IMAGE_DTO',
|
||||
payload: { imageDTO },
|
||||
};
|
||||
}
|
||||
}, [imageDTO]);
|
||||
|
||||
const droppableData = useMemo<InitialImageDropData>(
|
||||
() => ({ id: 'initial_image', actionType: 'SET_INITIAL_IMAGE' }),
|
||||
[]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (isConnected && isErrorControlImage) {
|
||||
onReset();
|
||||
}
|
||||
}, [onReset, isConnected, isErrorControlImage]);
|
||||
|
||||
return (
|
||||
<Flex w="full" alignItems="center" justifyContent="center">
|
||||
<Flex position="relative" w="full" h="full" alignItems="center" justifyContent="center">
|
||||
<IAIDndImage
|
||||
draggableData={draggableData}
|
||||
droppableData={droppableData}
|
||||
imageDTO={imageDTO}
|
||||
// postUploadAction={postUploadAction}
|
||||
/>
|
||||
|
||||
{imageDTO && (
|
||||
<Flex position="absolute" flexDir="column" top={1} insetInlineEnd={1} gap={1}>
|
||||
<IAIDndImageIcon
|
||||
onClick={onReset}
|
||||
icon={<PiArrowCounterClockwiseBold size={16} />}
|
||||
tooltip={t('controlnet.resetControlImage')}
|
||||
/>
|
||||
<IAIDndImageIcon
|
||||
onClick={onUseSize}
|
||||
icon={<PiRulerBold size={16} />}
|
||||
tooltip={
|
||||
shift ? t('controlnet.setControlImageDimensionsForce') : t('controlnet.setControlImageDimensions')
|
||||
}
|
||||
/>
|
||||
</Flex>
|
||||
)}
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
});
|
||||
|
||||
InitialImagePreview.displayName = 'InitialImagePreview';
|
@ -1,13 +0,0 @@
|
||||
import { CanvasEntitySettings } from 'features/controlLayers/components/common/CanvasEntitySettings';
|
||||
import { InitialImagePreview } from 'features/controlLayers/components/InitialImage/InitialImagePreview';
|
||||
import { memo } from 'react';
|
||||
|
||||
export const InitialImageSettings = memo(() => {
|
||||
return (
|
||||
<CanvasEntitySettings>
|
||||
<InitialImagePreview />
|
||||
</CanvasEntitySettings>
|
||||
);
|
||||
});
|
||||
|
||||
InitialImageSettings.displayName = 'InitialImageSettings';
|
@ -1,15 +0,0 @@
|
||||
import { Button } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import { sessionRequested } from 'features/controlLayers/store/canvasV2Slice';
|
||||
import { memo, useCallback } from 'react';
|
||||
|
||||
export const NewSessionButton = memo(() => {
|
||||
const dispatch = useAppDispatch();
|
||||
const onClick = useCallback(() => {
|
||||
dispatch(sessionRequested());
|
||||
}, [dispatch]);
|
||||
|
||||
return <Button onClick={onClick}>New</Button>;
|
||||
});
|
||||
|
||||
NewSessionButton.displayName = 'NewSessionButton';
|
@ -1,65 +0,0 @@
|
||||
import { CanvasImageRenderer } from 'features/controlLayers/konva/CanvasImage';
|
||||
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
|
||||
import type { InitialImageEntity } from 'features/controlLayers/store/types';
|
||||
import Konva from 'konva';
|
||||
|
||||
export class CanvasInitialImage {
|
||||
static NAME_PREFIX = 'initial-image';
|
||||
static LAYER_NAME = `${CanvasInitialImage.NAME_PREFIX}_layer`;
|
||||
static GROUP_NAME = `${CanvasInitialImage.NAME_PREFIX}_group`;
|
||||
static OBJECT_GROUP_NAME = `${CanvasInitialImage.NAME_PREFIX}_object-group`;
|
||||
|
||||
private state: InitialImageEntity;
|
||||
|
||||
id = 'initial_image';
|
||||
manager: CanvasManager;
|
||||
|
||||
konva: {
|
||||
layer: Konva.Layer;
|
||||
group: Konva.Group;
|
||||
objectGroup: Konva.Group;
|
||||
};
|
||||
|
||||
image: CanvasImageRenderer | null;
|
||||
|
||||
constructor(state: InitialImageEntity, manager: CanvasManager) {
|
||||
this.manager = manager;
|
||||
this.konva = {
|
||||
layer: new Konva.Layer({ name: CanvasInitialImage.LAYER_NAME, imageSmoothingEnabled: true, listening: false }),
|
||||
group: new Konva.Group({ name: CanvasInitialImage.GROUP_NAME, listening: false }),
|
||||
objectGroup: new Konva.Group({ name: CanvasInitialImage.OBJECT_GROUP_NAME, listening: false }),
|
||||
};
|
||||
this.konva.group.add(this.konva.objectGroup);
|
||||
this.konva.layer.add(this.konva.group);
|
||||
|
||||
this.image = null;
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
async render(state: InitialImageEntity) {
|
||||
this.state = state;
|
||||
|
||||
if (!this.state.imageObject) {
|
||||
this.konva.layer.visible(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.image) {
|
||||
this.image = new CanvasImageRenderer(this.state.imageObject);
|
||||
this.konva.objectGroup.add(this.image.konva.group);
|
||||
await this.image.update(this.state.imageObject, true);
|
||||
} else if (!this.image.isLoading && !this.image.isError) {
|
||||
await this.image.update(this.state.imageObject);
|
||||
}
|
||||
|
||||
if (this.state && this.state.isEnabled && !this.image?.isLoading && !this.image?.isError) {
|
||||
this.konva.layer.visible(true);
|
||||
} else {
|
||||
this.konva.layer.visible(false);
|
||||
}
|
||||
}
|
||||
|
||||
destroy(): void {
|
||||
this.konva.layer.destroy();
|
||||
}
|
||||
}
|
@ -6,7 +6,6 @@ import { PubSub } from 'common/util/PubSub/PubSub';
|
||||
import type { CanvasBrushLineRenderer } from 'features/controlLayers/konva/CanvasBrushLine';
|
||||
import type { CanvasEraserLineRenderer } from 'features/controlLayers/konva/CanvasEraserLine';
|
||||
import type { CanvasImageRenderer } from 'features/controlLayers/konva/CanvasImage';
|
||||
import { CanvasInitialImage } from 'features/controlLayers/konva/CanvasInitialImage';
|
||||
import { CanvasObjectRenderer } from 'features/controlLayers/konva/CanvasObjectRenderer';
|
||||
import { CanvasProgressPreview } from 'features/controlLayers/konva/CanvasProgressPreview';
|
||||
import type { CanvasRectRenderer } from 'features/controlLayers/konva/CanvasRect';
|
||||
@ -15,7 +14,6 @@ import {
|
||||
getCompositeLayerImage,
|
||||
getControlAdapterImage,
|
||||
getGenerationMode,
|
||||
getInitialImage,
|
||||
getInpaintMaskImage,
|
||||
getPrefixedId,
|
||||
getRegionMaskImage,
|
||||
@ -48,7 +46,6 @@ import { CanvasControlAdapter } from './CanvasControlAdapter';
|
||||
import { CanvasLayerAdapter } from './CanvasLayerAdapter';
|
||||
import { CanvasMaskAdapter } from './CanvasMaskAdapter';
|
||||
import { CanvasPreview } from './CanvasPreview';
|
||||
import type { CanvasRegion } from './CanvasRegion';
|
||||
import { CanvasStagingArea } from './CanvasStagingArea';
|
||||
import { CanvasStateApi } from './CanvasStateApi';
|
||||
import { CanvasTool } from './CanvasTool';
|
||||
@ -120,7 +117,6 @@ export class CanvasManager {
|
||||
layers: Map<string, CanvasLayerAdapter>;
|
||||
regions: Map<string, CanvasMaskAdapter>;
|
||||
inpaintMask: CanvasMaskAdapter;
|
||||
initialImage: CanvasInitialImage;
|
||||
util: Util;
|
||||
stateApi: CanvasStateApi;
|
||||
preview: CanvasPreview;
|
||||
@ -188,9 +184,6 @@ export class CanvasManager {
|
||||
this.regions = new Map();
|
||||
this.controlAdapters = new Map();
|
||||
|
||||
this.initialImage = new CanvasInitialImage(this.stateApi.getInitialImageState(), this);
|
||||
this.stage.add(this.initialImage.konva.layer);
|
||||
|
||||
this._worker.onmessage = (event: MessageEvent<ExtentsResult | WorkerLogMessage>) => {
|
||||
const { type, data } = event.data;
|
||||
if (type === 'log') {
|
||||
@ -250,10 +243,6 @@ export class CanvasManager {
|
||||
this._worker.postMessage(task, [data.buffer]);
|
||||
}
|
||||
|
||||
async renderInitialImage() {
|
||||
await this.initialImage.render(this.stateApi.getInitialImageState());
|
||||
}
|
||||
|
||||
async renderProgressPreview() {
|
||||
await this.preview.progressPreview.render(this.stateApi.$lastProgressEvent.get());
|
||||
}
|
||||
@ -286,7 +275,6 @@ export class CanvasManager {
|
||||
const regions = getRegionsState().entities;
|
||||
let zIndex = 0;
|
||||
this.background.konva.layer.zIndex(++zIndex);
|
||||
this.initialImage.konva.layer.zIndex(++zIndex);
|
||||
for (const layer of layers) {
|
||||
this.layers.get(layer.id)?.konva.layer.zIndex(++zIndex);
|
||||
}
|
||||
@ -322,7 +310,7 @@ export class CanvasManager {
|
||||
| CanvasRegionalGuidanceState
|
||||
| CanvasInpaintMaskState
|
||||
| null = null;
|
||||
let entityAdapter: CanvasLayerAdapter | CanvasControlAdapter | CanvasRegion | CanvasMaskAdapter | null = null;
|
||||
let entityAdapter: CanvasLayerAdapter | CanvasControlAdapter | CanvasMaskAdapter | null = null;
|
||||
|
||||
if (identifier.type === 'layer') {
|
||||
entityState = state.layers.entities.find((i) => i.id === identifier.id) ?? null;
|
||||
@ -470,17 +458,6 @@ export class CanvasManager {
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
this._isFirstRender ||
|
||||
state.initialImage !== this._prevState.initialImage ||
|
||||
state.bbox.rect !== this._prevState.bbox.rect ||
|
||||
state.tool.selected !== this._prevState.tool.selected ||
|
||||
state.selectedEntityIdentifier?.id !== this._prevState.selectedEntityIdentifier?.id
|
||||
) {
|
||||
this.log.debug('Rendering initial image');
|
||||
await this.renderInitialImage();
|
||||
}
|
||||
|
||||
if (
|
||||
this._isFirstRender ||
|
||||
state.regions.entities !== this._prevState.regions.entities ||
|
||||
@ -546,8 +523,7 @@ export class CanvasManager {
|
||||
if (
|
||||
this._isFirstRender ||
|
||||
state.bbox !== this._prevState.bbox ||
|
||||
state.tool.selected !== this._prevState.tool.selected ||
|
||||
state.session.isActive !== this._prevState.session.isActive
|
||||
state.tool.selected !== this._prevState.tool.selected
|
||||
) {
|
||||
this.log.debug('Rendering generation bbox');
|
||||
await this.preview.bbox.render();
|
||||
@ -656,18 +632,7 @@ export class CanvasManager {
|
||||
}
|
||||
|
||||
getGenerationMode(): GenerationMode {
|
||||
const session = this.stateApi.getSession();
|
||||
if (session.isActive) {
|
||||
return getGenerationMode({ manager: this });
|
||||
}
|
||||
|
||||
const initialImageState = this.stateApi.getInitialImageState();
|
||||
|
||||
if (initialImageState.imageObject && initialImageState.isEnabled) {
|
||||
return 'img2img';
|
||||
}
|
||||
|
||||
return 'txt2img';
|
||||
return getGenerationMode({ manager: this });
|
||||
}
|
||||
|
||||
getControlAdapterImage(arg: Omit<Parameters<typeof getControlAdapterImage>[0], 'manager'>) {
|
||||
@ -683,11 +648,7 @@ export class CanvasManager {
|
||||
}
|
||||
|
||||
getInitialImage(arg: Omit<Parameters<typeof getCompositeLayerImage>[0], 'manager'>) {
|
||||
if (this.stateApi.getSession().isActive) {
|
||||
return getCompositeLayerImage({ ...arg, manager: this });
|
||||
} else {
|
||||
return getInitialImage({ ...arg, manager: this });
|
||||
}
|
||||
return getCompositeLayerImage({ ...arg, manager: this });
|
||||
}
|
||||
|
||||
getLoggingContext() {
|
||||
|
@ -1,292 +0,0 @@
|
||||
import { rgbColorToString } from 'common/util/colorCodeTransformers';
|
||||
import { CanvasBrushLineRenderer } from 'features/controlLayers/konva/CanvasBrushLine';
|
||||
import { CanvasEraserLineRenderer } from 'features/controlLayers/konva/CanvasEraserLine';
|
||||
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
|
||||
import { CanvasRectRenderer } from 'features/controlLayers/konva/CanvasRect';
|
||||
import { getNodeBboxFast } from 'features/controlLayers/konva/entityBbox';
|
||||
import { mapId } from 'features/controlLayers/konva/util';
|
||||
import type {
|
||||
CanvasBrushLineState,
|
||||
CanvasEraserLineState,
|
||||
CanvasRectState,
|
||||
CanvasRegionalGuidanceState,
|
||||
} from 'features/controlLayers/store/types';
|
||||
import { isDrawingTool, RGBA_RED } from 'features/controlLayers/store/types';
|
||||
import Konva from 'konva';
|
||||
import { assert } from 'tsafe';
|
||||
|
||||
export class CanvasRegion {
|
||||
static NAME_PREFIX = 'region';
|
||||
static LAYER_NAME = `${CanvasRegion.NAME_PREFIX}_layer`;
|
||||
static TRANSFORMER_NAME = `${CanvasRegion.NAME_PREFIX}_transformer`;
|
||||
static GROUP_NAME = `${CanvasRegion.NAME_PREFIX}_group`;
|
||||
static OBJECT_GROUP_NAME = `${CanvasRegion.NAME_PREFIX}_object-group`;
|
||||
static COMPOSITING_RECT_NAME = `${CanvasRegion.NAME_PREFIX}_compositing-rect`;
|
||||
static TYPE = 'regional_guidance' as const;
|
||||
|
||||
private drawingBuffer: CanvasBrushLineState | CanvasEraserLineState | CanvasRectState | null;
|
||||
private state: CanvasRegionalGuidanceState;
|
||||
|
||||
id: string;
|
||||
type = CanvasRegion.TYPE;
|
||||
manager: CanvasManager;
|
||||
|
||||
konva: {
|
||||
layer: Konva.Layer;
|
||||
group: Konva.Group;
|
||||
objectGroup: Konva.Group;
|
||||
compositingRect: Konva.Rect;
|
||||
transformer: Konva.Transformer;
|
||||
};
|
||||
|
||||
objects: Map<string, CanvasBrushLineRenderer | CanvasEraserLineRenderer | CanvasRectRenderer>;
|
||||
|
||||
constructor(state: CanvasRegionalGuidanceState, manager: CanvasManager) {
|
||||
this.id = state.id;
|
||||
this.manager = manager;
|
||||
|
||||
this.konva = {
|
||||
layer: new Konva.Layer({ name: CanvasRegion.LAYER_NAME, listening: false }),
|
||||
group: new Konva.Group({ name: CanvasRegion.GROUP_NAME, listening: false }),
|
||||
objectGroup: new Konva.Group({ name: CanvasRegion.OBJECT_GROUP_NAME, listening: false }),
|
||||
transformer: new Konva.Transformer({
|
||||
name: CanvasRegion.TRANSFORMER_NAME,
|
||||
shouldOverdrawWholeArea: true,
|
||||
draggable: true,
|
||||
dragDistance: 0,
|
||||
enabledAnchors: ['top-left', 'top-right', 'bottom-left', 'bottom-right'],
|
||||
rotateEnabled: false,
|
||||
flipEnabled: false,
|
||||
}),
|
||||
compositingRect: new Konva.Rect({ name: CanvasRegion.COMPOSITING_RECT_NAME, listening: false }),
|
||||
};
|
||||
this.konva.group.add(this.konva.objectGroup);
|
||||
this.konva.layer.add(this.konva.group);
|
||||
this.konva.transformer.on('transformend', () => {
|
||||
this.manager.stateApi.onScaleChanged(
|
||||
{
|
||||
id: this.id,
|
||||
scale: this.konva.group.scaleX(),
|
||||
position: { x: this.konva.group.x(), y: this.konva.group.y() },
|
||||
},
|
||||
'regional_guidance'
|
||||
);
|
||||
});
|
||||
this.konva.transformer.on('dragend', () => {
|
||||
this.manager.stateApi.setEntityPosition(
|
||||
{ id: this.id, position: { x: this.konva.group.x(), y: this.konva.group.y() } },
|
||||
'regional_guidance'
|
||||
);
|
||||
});
|
||||
this.konva.layer.add(this.konva.transformer);
|
||||
this.konva.group.add(this.konva.compositingRect);
|
||||
this.objects = new Map();
|
||||
this.drawingBuffer = null;
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
destroy(): void {
|
||||
this.konva.layer.destroy();
|
||||
}
|
||||
|
||||
getDrawingBuffer() {
|
||||
return this.drawingBuffer;
|
||||
}
|
||||
|
||||
async setDrawingBuffer(obj: CanvasBrushLineState | CanvasEraserLineState | CanvasRectState | null) {
|
||||
this.drawingBuffer = obj;
|
||||
if (this.drawingBuffer) {
|
||||
if (this.drawingBuffer.type === 'brush_line') {
|
||||
this.drawingBuffer.color = RGBA_RED;
|
||||
} else if (this.drawingBuffer.type === 'rect') {
|
||||
this.drawingBuffer.color = RGBA_RED;
|
||||
}
|
||||
|
||||
await this.renderObject(this.drawingBuffer, true);
|
||||
this.updateGroup(true);
|
||||
}
|
||||
}
|
||||
|
||||
finalizeDrawingBuffer() {
|
||||
if (!this.drawingBuffer) {
|
||||
return;
|
||||
}
|
||||
if (this.drawingBuffer.type === 'brush_line') {
|
||||
this.manager.stateApi.addBrushLine({ id: this.id, brushLine: this.drawingBuffer }, 'regional_guidance');
|
||||
} else if (this.drawingBuffer.type === 'eraser_line') {
|
||||
this.manager.stateApi.addEraserLine({ id: this.id, eraserLine: this.drawingBuffer }, 'regional_guidance');
|
||||
} else if (this.drawingBuffer.type === 'rect') {
|
||||
this.manager.stateApi.addRect({ id: this.id, rect: this.drawingBuffer }, 'regional_guidance');
|
||||
}
|
||||
this.setDrawingBuffer(null);
|
||||
}
|
||||
|
||||
async render(state: CanvasRegionalGuidanceState) {
|
||||
this.state = state;
|
||||
|
||||
// Update the layer's position and listening state
|
||||
this.konva.group.setAttrs({
|
||||
x: state.position.x,
|
||||
y: state.position.y,
|
||||
scaleX: 1,
|
||||
scaleY: 1,
|
||||
});
|
||||
|
||||
let didDraw = false;
|
||||
|
||||
const objectIds = state.objects.map(mapId);
|
||||
// Destroy any objects that are no longer in state
|
||||
for (const object of this.objects.values()) {
|
||||
if (!objectIds.includes(object.id)) {
|
||||
this.objects.delete(object.id);
|
||||
object.destroy();
|
||||
didDraw = true;
|
||||
}
|
||||
}
|
||||
|
||||
for (const obj of state.objects) {
|
||||
if (await this.renderObject(obj)) {
|
||||
didDraw = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.drawingBuffer) {
|
||||
if (await this.renderObject(this.drawingBuffer)) {
|
||||
didDraw = true;
|
||||
}
|
||||
}
|
||||
|
||||
this.updateGroup(didDraw);
|
||||
}
|
||||
|
||||
private async renderObject(obj: CanvasRegionalGuidanceState['objects'][number], force = false): Promise<boolean> {
|
||||
if (obj.type === 'brush_line') {
|
||||
let brushLine = this.objects.get(obj.id);
|
||||
assert(brushLine instanceof CanvasBrushLineRenderer || brushLine === undefined);
|
||||
|
||||
if (!brushLine) {
|
||||
brushLine = new CanvasBrushLineRenderer(obj);
|
||||
this.objects.set(brushLine.id, brushLine);
|
||||
this.konva.objectGroup.add(brushLine.konva.group);
|
||||
return true;
|
||||
} else {
|
||||
if (brushLine.update(obj, force)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else if (obj.type === 'eraser_line') {
|
||||
let eraserLine = this.objects.get(obj.id);
|
||||
assert(eraserLine instanceof CanvasEraserLineRenderer || eraserLine === undefined);
|
||||
|
||||
if (!eraserLine) {
|
||||
eraserLine = new CanvasEraserLineRenderer(obj);
|
||||
this.objects.set(eraserLine.id, eraserLine);
|
||||
this.konva.objectGroup.add(eraserLine.konva.group);
|
||||
return true;
|
||||
} else {
|
||||
if (eraserLine.update(obj, force)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else if (obj.type === 'rect') {
|
||||
let rect = this.objects.get(obj.id);
|
||||
assert(rect instanceof CanvasRectRenderer || rect === undefined);
|
||||
|
||||
if (!rect) {
|
||||
rect = new CanvasRectRenderer(obj);
|
||||
this.objects.set(rect.id, rect);
|
||||
this.konva.objectGroup.add(rect.konva.group);
|
||||
return true;
|
||||
} else {
|
||||
if (rect.update(obj, force)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
updateGroup(didDraw: boolean) {
|
||||
this.konva.layer.visible(this.state.isEnabled);
|
||||
|
||||
// The user is allowed to reduce mask opacity to 0, but we need the opacity for the compositing rect to work
|
||||
this.konva.group.opacity(1);
|
||||
|
||||
if (didDraw) {
|
||||
// Convert the color to a string, stripping the alpha - the object group will handle opacity.
|
||||
const rgbColor = rgbColorToString(this.state.fill);
|
||||
const maskOpacity = this.manager.stateApi.getMaskOpacity();
|
||||
this.konva.compositingRect.setAttrs({
|
||||
// The rect should be the size of the layer - use the fast method if we don't have a pixel-perfect bbox already
|
||||
...getNodeBboxFast(this.konva.objectGroup),
|
||||
fill: rgbColor,
|
||||
opacity: maskOpacity,
|
||||
// Draw this rect only where there are non-transparent pixels under it (e.g. the mask shapes)
|
||||
globalCompositeOperation: 'source-in',
|
||||
// This rect must always be on top of all other shapes
|
||||
zIndex: this.objects.size + 1,
|
||||
});
|
||||
}
|
||||
|
||||
const isSelected = this.manager.stateApi.getIsSelected(this.id);
|
||||
const selectedTool = this.manager.stateApi.getToolState().selected;
|
||||
|
||||
if (this.objects.size === 0) {
|
||||
// If the layer is totally empty, reset the cache and bail out.
|
||||
this.konva.layer.listening(false);
|
||||
this.konva.transformer.nodes([]);
|
||||
if (this.konva.group.isCached()) {
|
||||
this.konva.group.clearCache();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (isSelected && selectedTool === 'move') {
|
||||
// When the layer is selected and being moved, we should always cache it.
|
||||
// We should update the cache if we drew to the layer.
|
||||
if (!this.konva.group.isCached() || didDraw) {
|
||||
this.konva.group.cache();
|
||||
}
|
||||
// Activate the transformer
|
||||
this.konva.layer.listening(true);
|
||||
this.konva.transformer.nodes([this.konva.group]);
|
||||
this.konva.transformer.forceUpdate();
|
||||
return;
|
||||
}
|
||||
|
||||
if (isSelected && selectedTool !== 'move') {
|
||||
// If the layer is selected but not using the move tool, we don't want the layer to be listening.
|
||||
this.konva.layer.listening(false);
|
||||
// The transformer also does not need to be active.
|
||||
this.konva.transformer.nodes([]);
|
||||
if (isDrawingTool(selectedTool)) {
|
||||
// We are using a drawing tool (brush, eraser, rect). These tools change the layer's rendered appearance, so we
|
||||
// should never be cached.
|
||||
if (this.konva.group.isCached()) {
|
||||
this.konva.group.clearCache();
|
||||
}
|
||||
} else {
|
||||
// We are using a non-drawing tool (move, view, bbox), so we should cache the layer.
|
||||
// We should update the cache if we drew to the layer.
|
||||
if (!this.konva.group.isCached() || didDraw) {
|
||||
this.konva.group.cache();
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isSelected) {
|
||||
// Unselected layers should not be listening
|
||||
this.konva.layer.listening(false);
|
||||
// The transformer also does not need to be active.
|
||||
this.konva.transformer.nodes([]);
|
||||
// Update the layer's cache if it's not already cached or we drew to it.
|
||||
if (!this.konva.group.isCached() || didDraw) {
|
||||
this.konva.group.cache();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
@ -188,9 +188,6 @@ export class CanvasStateApi {
|
||||
getInpaintMaskState = () => {
|
||||
return this.getState().inpaintMask;
|
||||
};
|
||||
getInitialImageState = () => {
|
||||
return this.getState().initialImage;
|
||||
};
|
||||
getMaskOpacity = () => {
|
||||
return this.getState().settings.maskOpacity;
|
||||
};
|
||||
|
@ -1,250 +0,0 @@
|
||||
import openBase64ImageInTab from 'common/util/openBase64ImageInTab';
|
||||
import { getLayerBboxId } from 'features/controlLayers/konva/naming';
|
||||
import { imageDataToDataURL } from 'features/controlLayers/konva/util';
|
||||
import type {
|
||||
BboxChangedArg,
|
||||
CanvasControlAdapterState,
|
||||
CanvasEntityState,
|
||||
CanvasLayerState,
|
||||
CanvasRegionalGuidanceState,
|
||||
} from 'features/controlLayers/store/types';
|
||||
import Konva from 'konva';
|
||||
import type { IRect } from 'konva/lib/types';
|
||||
import { assert } from 'tsafe';
|
||||
|
||||
/**
|
||||
* Creates a bounding box rect for a layer.
|
||||
* @param entity The layer state for the layer to create the bounding box for
|
||||
* @param konvaLayer The konva layer to attach the bounding box to
|
||||
*/
|
||||
export const createBboxRect = (entity: CanvasEntityState, konvaLayer: Konva.Layer): Konva.Rect => {
|
||||
const rect = new Konva.Rect({
|
||||
id: getLayerBboxId(entity.id),
|
||||
name: 'bbox',
|
||||
strokeWidth: 1,
|
||||
visible: false,
|
||||
});
|
||||
konvaLayer.add(rect);
|
||||
return rect;
|
||||
};
|
||||
|
||||
/**
|
||||
* Logic to create and render bounding boxes for layers.
|
||||
* Some utils are included for calculating bounding boxes.
|
||||
*/
|
||||
|
||||
type Extents = {
|
||||
minX: number;
|
||||
minY: number;
|
||||
maxX: number;
|
||||
maxY: number;
|
||||
};
|
||||
|
||||
const GET_CLIENT_RECT_CONFIG = { skipTransform: true };
|
||||
|
||||
/**
|
||||
* Get the bounding box of an image.
|
||||
* @param imageData The ImageData object to get the bounding box of.
|
||||
* @returns The minimum and maximum x and y values of the image's bounding box, or null if the image has no pixels.
|
||||
*/
|
||||
export const getImageDataBbox = (imageData: ImageData): Extents | null => {
|
||||
const { data, width, height } = imageData;
|
||||
let minX = width;
|
||||
let minY = height;
|
||||
let maxX = -1;
|
||||
let maxY = -1;
|
||||
let alpha = 0;
|
||||
let isEmpty = true;
|
||||
|
||||
for (let y = 0; y < height; y++) {
|
||||
for (let x = 0; x < width; x++) {
|
||||
alpha = data[(y * width + x) * 4 + 3] ?? 0;
|
||||
if (alpha > 0) {
|
||||
isEmpty = false;
|
||||
if (x < minX) {
|
||||
minX = x;
|
||||
}
|
||||
if (x > maxX) {
|
||||
maxX = x;
|
||||
}
|
||||
if (y < minY) {
|
||||
minY = y;
|
||||
}
|
||||
if (y > maxY) {
|
||||
maxY = y;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return isEmpty ? null : { minX, minY, maxX: maxX + 1, maxY: maxY + 1 };
|
||||
};
|
||||
|
||||
/**
|
||||
* Clones a regional guidance konva layer onto an offscreen stage/canvas. This allows the pixel data for a given layer
|
||||
* to be captured, manipulated or analyzed without interference from other layers.
|
||||
* @param layer The konva layer to clone.
|
||||
* @param filterChildren A callback to filter out unwanted children
|
||||
* @returns The cloned stage and layer.
|
||||
*/
|
||||
const getIsolatedLayerClone = (
|
||||
layer: Konva.Layer,
|
||||
filterChildren: (node: Konva.Node) => boolean
|
||||
): { stageClone: Konva.Stage; layerClone: Konva.Layer } => {
|
||||
const stage = layer.getStage();
|
||||
|
||||
// Construct an offscreen canvas with the same dimensions as the layer's stage.
|
||||
const offscreenStageContainer = document.createElement('div');
|
||||
const stageClone = new Konva.Stage({
|
||||
container: offscreenStageContainer,
|
||||
x: stage.x(),
|
||||
y: stage.y(),
|
||||
width: stage.width(),
|
||||
height: stage.height(),
|
||||
});
|
||||
|
||||
// Clone the layer and filter out unwanted children.
|
||||
const layerClone = layer.clone();
|
||||
stageClone.add(layerClone);
|
||||
|
||||
for (const child of layerClone.getChildren()) {
|
||||
if (filterChildren(child) && child.hasChildren()) {
|
||||
// We need to cache the group to ensure it composites out eraser strokes correctly
|
||||
child.opacity(1);
|
||||
child.cache();
|
||||
} else {
|
||||
// Filter out unwanted children.
|
||||
child.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
return { stageClone, layerClone };
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the bounding box of a regional prompt konva layer. This function has special handling for regional prompt layers.
|
||||
* @param layer The konva layer to get the bounding box of.
|
||||
* @param preview Whether to open a new tab displaying the rendered layer, which is used to calculate the bbox.
|
||||
*/
|
||||
const getLayerBboxPixels = (
|
||||
layer: Konva.Layer,
|
||||
filterChildren: (node: Konva.Node) => boolean,
|
||||
preview: boolean = false
|
||||
): IRect | null => {
|
||||
// To calculate the layer's bounding box, we must first export it to a pixel array, then do some math.
|
||||
//
|
||||
// Though it is relatively fast, we can't use Konva's `getClientRect`. It programmatically determines the rect
|
||||
// by calculating the extents of individual shapes from their "vector" shape data.
|
||||
//
|
||||
// This doesn't work when some shapes are drawn with composite operations that "erase" pixels, like eraser lines.
|
||||
// These shapes' extents are still calculated as if they were solid, leading to a bounding box that is too large.
|
||||
const { stageClone, layerClone } = getIsolatedLayerClone(layer, filterChildren);
|
||||
|
||||
// Get a worst-case rect using the relatively fast `getClientRect`.
|
||||
const layerRect = layerClone.getClientRect();
|
||||
if (layerRect.width === 0 || layerRect.height === 0) {
|
||||
return null;
|
||||
}
|
||||
// Capture the image data with the above rect.
|
||||
const layerImageData = stageClone
|
||||
.toCanvas(layerRect)
|
||||
.getContext('2d')
|
||||
?.getImageData(0, 0, layerRect.width, layerRect.height);
|
||||
assert(layerImageData, "Unable to get layer's image data");
|
||||
|
||||
if (preview) {
|
||||
openBase64ImageInTab([{ base64: imageDataToDataURL(layerImageData), caption: layer.id() }]);
|
||||
}
|
||||
|
||||
// Calculate the layer's bounding box.
|
||||
const layerBbox = getImageDataBbox(layerImageData);
|
||||
|
||||
if (!layerBbox) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Correct the bounding box to be relative to the layer's position.
|
||||
const correctedLayerBbox = {
|
||||
x: layerBbox.minX - Math.floor(stageClone.x()) + layerRect.x - Math.floor(layer.x()),
|
||||
y: layerBbox.minY - Math.floor(stageClone.y()) + layerRect.y - Math.floor(layer.y()),
|
||||
width: layerBbox.maxX - layerBbox.minX,
|
||||
height: layerBbox.maxY - layerBbox.minY,
|
||||
};
|
||||
|
||||
return correctedLayerBbox;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the bounding box of a konva node. This function is faster than `getLayerBboxPixels` but less accurate. It
|
||||
* should only be used when there are no eraser strokes or shapes in the node.
|
||||
* @param node The konva node to get the bounding box of.
|
||||
* @returns The bounding box of the node.
|
||||
*/
|
||||
export const getNodeBboxFast = (node: Konva.Node): IRect => {
|
||||
const bbox = node.getClientRect(GET_CLIENT_RECT_CONFIG);
|
||||
return bbox;
|
||||
};
|
||||
|
||||
// TODO(psyche): fix this
|
||||
const filterRGChildren = (node: Konva.Node): boolean => true;
|
||||
const filterLayerChildren = (node: Konva.Node): boolean => true;
|
||||
const filterCAChildren = (node: Konva.Node): boolean => true;
|
||||
|
||||
/**
|
||||
* Calculates the bbox of each regional guidance layer. Only calculates if the mask has changed.
|
||||
* @param stage The konva stage
|
||||
* @param entityStates An array of layers to calculate bboxes for
|
||||
* @param onBboxChanged Callback for when the bounding box changes
|
||||
*/
|
||||
export const updateBboxes = (
|
||||
stage: Konva.Stage,
|
||||
layers: CanvasLayerState[],
|
||||
controlAdapters: CanvasControlAdapterState[],
|
||||
regions: CanvasRegionalGuidanceState[],
|
||||
onBboxChanged: (arg: BboxChangedArg, entityType: CanvasEntityState['type']) => void
|
||||
): void => {
|
||||
for (const entityState of [...layers, ...controlAdapters, ...regions]) {
|
||||
const konvaLayer = stage.findOne<Konva.Layer>(`#${entityState.id}`);
|
||||
assert(konvaLayer, `Layer ${entityState.id} not found in stage`);
|
||||
// We only need to recalculate the bbox if the layer has changed
|
||||
if (entityState.bboxNeedsUpdate) {
|
||||
const bboxRect = konvaLayer.findOne<Konva.Rect>('.bbox') ?? createBboxRect(entityState, konvaLayer);
|
||||
|
||||
// Hide the bbox while we calculate the new bbox, else the bbox will be included in the calculation
|
||||
const visible = bboxRect.visible();
|
||||
bboxRect.visible(false);
|
||||
|
||||
if (entityState.type === 'layer') {
|
||||
if (entityState.objects.length === 0) {
|
||||
// No objects - no bbox to calculate
|
||||
onBboxChanged({ id: entityState.id, bbox: null }, 'layer');
|
||||
} else {
|
||||
onBboxChanged({ id: entityState.id, bbox: getLayerBboxPixels(konvaLayer, filterLayerChildren) }, 'layer');
|
||||
}
|
||||
} else if (entityState.type === 'control_adapter') {
|
||||
if (!entityState.imageObject && !entityState.processedImageObject) {
|
||||
// No objects - no bbox to calculate
|
||||
onBboxChanged({ id: entityState.id, bbox: null }, 'control_adapter');
|
||||
} else {
|
||||
onBboxChanged(
|
||||
{ id: entityState.id, bbox: getLayerBboxPixels(konvaLayer, filterCAChildren) },
|
||||
'control_adapter'
|
||||
);
|
||||
}
|
||||
} else if (entityState.type === 'regional_guidance') {
|
||||
if (entityState.objects.length === 0) {
|
||||
// No objects - no bbox to calculate
|
||||
onBboxChanged({ id: entityState.id, bbox: null }, 'regional_guidance');
|
||||
} else {
|
||||
onBboxChanged(
|
||||
{ id: entityState.id, bbox: getLayerBboxPixels(konvaLayer, filterRGChildren) },
|
||||
'regional_guidance'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Restore the visibility of the bbox
|
||||
bboxRect.visible(visible);
|
||||
}
|
||||
}
|
||||
};
|
@ -5,7 +5,6 @@ import { deepClone } from 'common/util/deepClone';
|
||||
import { bboxReducers } from 'features/controlLayers/store/bboxReducers';
|
||||
import { compositingReducers } from 'features/controlLayers/store/compositingReducers';
|
||||
import { controlAdaptersReducers } from 'features/controlLayers/store/controlAdaptersReducers';
|
||||
import { initialImageReducers } from 'features/controlLayers/store/initialImageReducers';
|
||||
import { inpaintMaskReducers } from 'features/controlLayers/store/inpaintMaskReducers';
|
||||
import { ipAdaptersReducers } from 'features/controlLayers/store/ipAdaptersReducers';
|
||||
import { layersReducers } from 'features/controlLayers/store/layersReducers';
|
||||
@ -33,14 +32,6 @@ const initialState: CanvasV2State = {
|
||||
ipAdapters: { entities: [] },
|
||||
regions: { entities: [] },
|
||||
loras: [],
|
||||
initialImage: {
|
||||
id: 'initial_image',
|
||||
type: 'initial_image',
|
||||
bbox: null,
|
||||
bboxNeedsUpdate: false,
|
||||
isEnabled: true,
|
||||
imageObject: null,
|
||||
},
|
||||
inpaintMask: {
|
||||
id: 'inpaint_mask',
|
||||
type: 'inpaint_mask',
|
||||
@ -125,7 +116,6 @@ const initialState: CanvasV2State = {
|
||||
refinerStart: 0.8,
|
||||
},
|
||||
session: {
|
||||
isActive: false,
|
||||
isStaging: false,
|
||||
stagedImages: [],
|
||||
selectedStagedImageIndex: 0,
|
||||
@ -148,7 +138,6 @@ export const canvasV2Slice = createSlice({
|
||||
...bboxReducers,
|
||||
...inpaintMaskReducers,
|
||||
...sessionReducers,
|
||||
...initialImageReducers,
|
||||
entitySelected: (state, action: PayloadAction<CanvasEntityIdentifier>) => {
|
||||
state.selectedEntityIdentifier = action.payload;
|
||||
},
|
||||
@ -175,7 +164,6 @@ export const canvasV2Slice = createSlice({
|
||||
state.session = deepClone(initialState.session);
|
||||
state.tool = deepClone(initialState.tool);
|
||||
state.inpaintMask = deepClone(initialState.inpaintMask);
|
||||
state.initialImage = deepClone(initialState.initialImage);
|
||||
},
|
||||
},
|
||||
});
|
||||
@ -342,18 +330,12 @@ export const {
|
||||
imRectAdded,
|
||||
inpaintMaskRasterized,
|
||||
// Staging
|
||||
sessionStarted,
|
||||
sessionStartedStaging,
|
||||
sessionImageStaged,
|
||||
sessionStagedImageDiscarded,
|
||||
sessionStagingAreaReset,
|
||||
sessionNextStagedImageSelected,
|
||||
sessionPrevStagedImageSelected,
|
||||
// Initial image
|
||||
iiRecalled,
|
||||
iiIsEnabledToggled,
|
||||
iiReset,
|
||||
iiImageChanged,
|
||||
} = canvasV2Slice.actions;
|
||||
|
||||
export const selectCanvasV2Slice = (state: RootState) => state.canvasV2;
|
||||
|
@ -1,38 +0,0 @@
|
||||
import type { PayloadAction, SliceCaseReducers } from '@reduxjs/toolkit';
|
||||
import { isEqual } from 'lodash-es';
|
||||
import type { ImageDTO } from 'services/api/types';
|
||||
|
||||
import type { CanvasV2State, InitialImageEntity } from './types';
|
||||
import { imageDTOToImageObject } from './types';
|
||||
|
||||
export const initialImageReducers = {
|
||||
iiRecalled: (state, action: PayloadAction<{ data: InitialImageEntity }>) => {
|
||||
const { data } = action.payload;
|
||||
state.initialImage = data;
|
||||
state.selectedEntityIdentifier = { type: 'initial_image', id: 'initial_image' };
|
||||
},
|
||||
iiIsEnabledToggled: (state) => {
|
||||
if (!state.initialImage) {
|
||||
return;
|
||||
}
|
||||
state.initialImage.isEnabled = !state.initialImage.isEnabled;
|
||||
},
|
||||
iiReset: (state) => {
|
||||
state.initialImage.imageObject = null;
|
||||
},
|
||||
iiImageChanged: (state, action: PayloadAction<{ imageDTO: ImageDTO }>) => {
|
||||
const { imageDTO } = action.payload;
|
||||
if (!state.initialImage) {
|
||||
return;
|
||||
}
|
||||
const newImageObject = imageDTOToImageObject(imageDTO);
|
||||
if (isEqual(newImageObject, state.initialImage.imageObject)) {
|
||||
return;
|
||||
}
|
||||
state.initialImage.bbox = null;
|
||||
state.initialImage.bboxNeedsUpdate = true;
|
||||
state.initialImage.isEnabled = true;
|
||||
state.initialImage.imageObject = newImageObject;
|
||||
state.selectedEntityIdentifier = { type: 'initial_image', id: 'initial_image' };
|
||||
},
|
||||
} satisfies SliceCaseReducers<CanvasV2State>;
|
@ -2,10 +2,6 @@ import type { PayloadAction, SliceCaseReducers } from '@reduxjs/toolkit';
|
||||
import type { CanvasV2State, StagingAreaImage } from 'features/controlLayers/store/types';
|
||||
|
||||
export const sessionReducers = {
|
||||
sessionStarted: (state) => {
|
||||
state.session.isActive = true;
|
||||
state.selectedEntityIdentifier = { id: 'inpaint_mask', type: 'inpaint_mask' };
|
||||
},
|
||||
sessionStartedStaging: (state) => {
|
||||
state.session.isStaging = true;
|
||||
state.session.selectedStagedImageIndex = 0;
|
||||
|
@ -2,7 +2,6 @@ import type { JSONObject } from 'common/types';
|
||||
import type { CanvasControlAdapter } from 'features/controlLayers/konva/CanvasControlAdapter';
|
||||
import { CanvasLayerAdapter } from 'features/controlLayers/konva/CanvasLayerAdapter';
|
||||
import { CanvasMaskAdapter } from 'features/controlLayers/konva/CanvasMaskAdapter';
|
||||
import { CanvasRegion } from 'features/controlLayers/konva/CanvasRegion';
|
||||
import { getObjectId } from 'features/controlLayers/konva/util';
|
||||
import { zModelIdentifierField } from 'features/nodes/types/common';
|
||||
import type { AspectRatioState } from 'features/parameters/components/DocumentSize/types';
|
||||
@ -686,16 +685,6 @@ const zCanvasInpaintMaskState = z.object({
|
||||
});
|
||||
export type CanvasInpaintMaskState = z.infer<typeof zCanvasInpaintMaskState>;
|
||||
|
||||
const zInitialImageEntity = z.object({
|
||||
id: z.literal('initial_image'),
|
||||
type: z.literal('initial_image'),
|
||||
isEnabled: z.boolean(),
|
||||
bbox: zRect.nullable(),
|
||||
bboxNeedsUpdate: z.boolean(),
|
||||
imageObject: zCanvasImageState.nullable(),
|
||||
});
|
||||
export type InitialImageEntity = z.infer<typeof zInitialImageEntity>;
|
||||
|
||||
const zCanvasControlAdapterStateBase = z.object({
|
||||
id: zId,
|
||||
type: z.literal('control_adapter'),
|
||||
@ -818,8 +807,7 @@ export type CanvasEntityState =
|
||||
| CanvasControlAdapterState
|
||||
| CanvasRegionalGuidanceState
|
||||
| CanvasInpaintMaskState
|
||||
| CanvasIPAdapterState
|
||||
| InitialImageEntity;
|
||||
| CanvasIPAdapterState;
|
||||
export type CanvasEntityIdentifier = Pick<CanvasEntityState, 'id' | 'type'>;
|
||||
|
||||
export type LoRA = {
|
||||
@ -847,7 +835,6 @@ export type CanvasV2State = {
|
||||
ipAdapters: { entities: CanvasIPAdapterState[] };
|
||||
regions: { entities: CanvasRegionalGuidanceState[] };
|
||||
loras: LoRA[];
|
||||
initialImage: InitialImageEntity;
|
||||
tool: {
|
||||
selected: Tool;
|
||||
selectedBuffer: Tool | null;
|
||||
@ -920,7 +907,6 @@ export type CanvasV2State = {
|
||||
refinerStart: number;
|
||||
};
|
||||
session: {
|
||||
isActive: boolean;
|
||||
isStaging: boolean;
|
||||
stagedImages: StagingAreaImage[];
|
||||
selectedStagedImageIndex: number;
|
||||
@ -969,11 +955,9 @@ export function isDrawableEntity(
|
||||
}
|
||||
|
||||
export function isDrawableEntityAdapter(
|
||||
adapter: CanvasLayerAdapter | CanvasRegion | CanvasControlAdapter | CanvasMaskAdapter
|
||||
): adapter is CanvasLayerAdapter | CanvasRegion | CanvasMaskAdapter {
|
||||
return (
|
||||
adapter instanceof CanvasLayerAdapter || adapter instanceof CanvasRegion || adapter instanceof CanvasMaskAdapter
|
||||
);
|
||||
adapter: CanvasLayerAdapter | CanvasControlAdapter | CanvasMaskAdapter
|
||||
): adapter is CanvasLayerAdapter | CanvasMaskAdapter {
|
||||
return adapter instanceof CanvasLayerAdapter || adapter instanceof CanvasMaskAdapter;
|
||||
}
|
||||
|
||||
export function isDrawableEntityType(
|
||||
|
Loading…
Reference in New Issue
Block a user