mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
feat(ui): rough out img2img on canvas
This commit is contained in:
parent
78b4562184
commit
551dd393aa
@ -4,6 +4,7 @@ import type { AppStartListening } from 'app/store/middleware/listenerMiddleware'
|
||||
import { parseify } from 'common/util/serialize';
|
||||
import {
|
||||
caImageChanged,
|
||||
iiImageChanged,
|
||||
ipaImageChanged,
|
||||
layerImageAdded,
|
||||
rgIPAdapterImageChanged,
|
||||
@ -110,6 +111,18 @@ export const addImageDroppedListener = (startAppListening: AppStartListening) =>
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Image dropped on Raster layer
|
||||
*/
|
||||
if (
|
||||
overData.actionType === 'SET_INITIAL_IMAGE' &&
|
||||
activeData.payloadType === 'IMAGE_DTO' &&
|
||||
activeData.payload.imageDTO
|
||||
) {
|
||||
dispatch(iiImageChanged({ imageDTO: activeData.payload.imageDTO }));
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Image dropped on node image field
|
||||
*/
|
||||
|
@ -4,6 +4,7 @@ import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { AddLayerButton } from 'features/controlLayers/components/AddLayerButton';
|
||||
import { CanvasEntityList } from 'features/controlLayers/components/CanvasEntityList';
|
||||
import { DeleteAllLayersButton } from 'features/controlLayers/components/DeleteAllLayersButton';
|
||||
import { InitialImage } from 'features/controlLayers/components/InitialImage/InitialImage';
|
||||
import { IM } from 'features/controlLayers/components/InpaintMask/IM';
|
||||
import { memo } from 'react';
|
||||
|
||||
@ -17,6 +18,7 @@ export const ControlLayersPanelContent = memo(() => {
|
||||
</Flex>
|
||||
{isCanvasSessionActive && <IM />}
|
||||
<CanvasEntityList />
|
||||
{!isCanvasSessionActive && <InitialImage />}
|
||||
</Flex>
|
||||
);
|
||||
});
|
||||
|
@ -0,0 +1,25 @@
|
||||
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';
|
@ -0,0 +1,34 @@
|
||||
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';
|
@ -0,0 +1,100 @@
|
||||
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 { documentHeightChanged, documentWidthChanged, 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(documentWidthChanged({ width, ...options }));
|
||||
dispatch(documentHeightChanged({ height, ...options }));
|
||||
} else {
|
||||
const { width, height } = calculateNewSize(imageDTO.width / imageDTO.height, optimalDimension * optimalDimension);
|
||||
dispatch(documentWidthChanged({ width, ...options }));
|
||||
dispatch(documentHeightChanged({ 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';
|
@ -0,0 +1,13 @@
|
||||
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';
|
@ -0,0 +1,73 @@
|
||||
import { CanvasImage } from 'features/controlLayers/konva/CanvasImage';
|
||||
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
|
||||
import { getObjectGroupId } from 'features/controlLayers/konva/naming';
|
||||
import type { InitialImageEntity } from 'features/controlLayers/store/types';
|
||||
import Konva from 'konva';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
export class CanvasInitialImage {
|
||||
id = 'initial_image';
|
||||
manager: CanvasManager;
|
||||
layer: Konva.Layer;
|
||||
group: Konva.Group;
|
||||
objectsGroup: Konva.Group;
|
||||
image: CanvasImage | null;
|
||||
private initialImageState: InitialImageEntity;
|
||||
|
||||
constructor(initialImageState: InitialImageEntity, manager: CanvasManager) {
|
||||
this.manager = manager;
|
||||
this.layer = new Konva.Layer({
|
||||
id: this.id,
|
||||
imageSmoothingEnabled: true,
|
||||
listening: false,
|
||||
});
|
||||
this.group = new Konva.Group({
|
||||
id: getObjectGroupId(this.layer.id(), uuidv4()),
|
||||
listening: false,
|
||||
});
|
||||
this.objectsGroup = new Konva.Group({ listening: false });
|
||||
this.group.add(this.objectsGroup);
|
||||
this.layer.add(this.group);
|
||||
|
||||
this.image = null;
|
||||
this.initialImageState = initialImageState;
|
||||
}
|
||||
|
||||
async render(initialImageState: InitialImageEntity) {
|
||||
this.initialImageState = initialImageState;
|
||||
|
||||
if (!this.initialImageState.imageObject) {
|
||||
this.layer.visible(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const imageObject = this.initialImageState.imageObject;
|
||||
|
||||
if (!imageObject) {
|
||||
if (this.image) {
|
||||
this.image.konvaImageGroup.visible(false);
|
||||
}
|
||||
} else if (!this.image) {
|
||||
this.image = await new CanvasImage(imageObject, {
|
||||
onLoad: () => {
|
||||
this.updateGroup();
|
||||
},
|
||||
});
|
||||
this.objectsGroup.add(this.image.konvaImageGroup);
|
||||
await this.image.updateImageSource(imageObject.image.name);
|
||||
} else if (!this.image.isLoading && !this.image.isError) {
|
||||
await this.image.update(imageObject);
|
||||
}
|
||||
|
||||
this.updateGroup();
|
||||
}
|
||||
|
||||
updateGroup() {
|
||||
const visible = this.initialImageState ? this.initialImageState.isEnabled : false;
|
||||
this.layer.visible(visible);
|
||||
}
|
||||
|
||||
destroy(): void {
|
||||
this.layer.destroy();
|
||||
}
|
||||
}
|
@ -13,7 +13,7 @@ import { assert } from 'tsafe';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
export class CanvasInpaintMask {
|
||||
id: string;
|
||||
id = 'inpaint_mask';
|
||||
manager: CanvasManager;
|
||||
layer: Konva.Layer;
|
||||
group: Konva.Group;
|
||||
@ -25,7 +25,6 @@ export class CanvasInpaintMask {
|
||||
private inpaintMaskState: InpaintMaskEntity;
|
||||
|
||||
constructor(entity: InpaintMaskEntity, manager: CanvasManager) {
|
||||
this.id = 'inpaint_mask';
|
||||
this.manager = manager;
|
||||
this.layer = new Konva.Layer({ id: this.id });
|
||||
|
||||
|
@ -1,15 +1,17 @@
|
||||
import type { Store } from '@reduxjs/toolkit';
|
||||
import { logger } from 'app/logging/logger';
|
||||
import type { RootState } from 'app/store/store';
|
||||
import { CanvasInitialImage } from 'features/controlLayers/konva/CanvasInitialImage';
|
||||
import {
|
||||
getCompositeLayerImage,
|
||||
getControlAdapterImage,
|
||||
getGenerationMode,
|
||||
getImageSourceImage,
|
||||
getInitialImage,
|
||||
getInpaintMaskImage,
|
||||
getRegionMaskImage,
|
||||
} from 'features/controlLayers/konva/util';
|
||||
import { $lastProgressEvent, $shouldShowStagedImage } from 'features/controlLayers/store/canvasV2Slice';
|
||||
import type { CanvasV2State } from 'features/controlLayers/store/types';
|
||||
import type { CanvasV2State, GenerationMode } from 'features/controlLayers/store/types';
|
||||
import type Konva from 'konva';
|
||||
import { atom } from 'nanostores';
|
||||
import { getImageDTO as defaultGetImageDTO, uploadImage as defaultUploadImage } from 'services/api/endpoints/images';
|
||||
@ -58,6 +60,7 @@ export class CanvasManager {
|
||||
layers: Map<string, CanvasLayer>;
|
||||
regions: Map<string, CanvasRegion>;
|
||||
inpaintMask: CanvasInpaintMask;
|
||||
initialImage: CanvasInitialImage;
|
||||
util: Util;
|
||||
stateApi: CanvasStateApi;
|
||||
preview: CanvasPreview;
|
||||
@ -102,6 +105,13 @@ export class CanvasManager {
|
||||
this.layers = new Map();
|
||||
this.regions = new Map();
|
||||
this.controlAdapters = new Map();
|
||||
|
||||
this.initialImage = new CanvasInitialImage(this.stateApi.getInitialImageState(), this);
|
||||
this.stage.add(this.initialImage.layer);
|
||||
}
|
||||
|
||||
async renderInitialImage() {
|
||||
this.initialImage.render(this.stateApi.getInitialImageState());
|
||||
}
|
||||
|
||||
async renderLayers() {
|
||||
@ -180,6 +190,7 @@ export class CanvasManager {
|
||||
const regions = getRegionsState().entities;
|
||||
let zIndex = 0;
|
||||
this.background.layer.zIndex(++zIndex);
|
||||
this.initialImage.layer.zIndex(++zIndex);
|
||||
for (const layer of layers) {
|
||||
this.layers.get(layer.id)?.layer.zIndex(++zIndex);
|
||||
}
|
||||
@ -225,6 +236,17 @@ export class CanvasManager {
|
||||
this.renderLayers();
|
||||
}
|
||||
|
||||
if (
|
||||
this.isFirstRender ||
|
||||
state.initialImage !== this.prevState.initialImage ||
|
||||
state.document !== this.prevState.document ||
|
||||
state.tool.selected !== this.prevState.tool.selected ||
|
||||
state.selectedEntityIdentifier?.id !== this.prevState.selectedEntityIdentifier?.id
|
||||
) {
|
||||
log.debug('Rendering intial image');
|
||||
this.renderInitialImage();
|
||||
}
|
||||
|
||||
if (
|
||||
this.isFirstRender ||
|
||||
state.regions.entities !== this.prevState.regions.entities ||
|
||||
@ -367,8 +389,19 @@ export class CanvasManager {
|
||||
}
|
||||
};
|
||||
|
||||
getGenerationMode() {
|
||||
return getGenerationMode({ manager: this });
|
||||
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';
|
||||
}
|
||||
|
||||
getControlAdapterImage(arg: Omit<Parameters<typeof getControlAdapterImage>[0], 'manager'>) {
|
||||
@ -383,7 +416,11 @@ export class CanvasManager {
|
||||
return getInpaintMaskImage({ ...arg, manager: this });
|
||||
}
|
||||
|
||||
getImageSourceImage(arg: Omit<Parameters<typeof getImageSourceImage>[0], 'manager'>) {
|
||||
return getImageSourceImage({ ...arg, manager: this });
|
||||
getInitialImage(arg: Omit<Parameters<typeof getCompositeLayerImage>[0], 'manager'>) {
|
||||
if (this.stateApi.getSession().isActive) {
|
||||
return getCompositeLayerImage({ ...arg, manager: this });
|
||||
} else {
|
||||
return getInitialImage({ ...arg, manager: this });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -228,6 +228,9 @@ export class CanvasStateApi {
|
||||
getInpaintMaskState = () => {
|
||||
return this.getState().inpaintMask;
|
||||
};
|
||||
getInitialImageState = () => {
|
||||
return this.getState().initialImage;
|
||||
};
|
||||
getMaskOpacity = () => {
|
||||
return this.getState().settings.maskOpacity;
|
||||
};
|
||||
|
@ -317,6 +317,23 @@ export function getControlAdapterLayerClone(arg: { manager: CanvasManager; id: s
|
||||
return controlAdapterClone;
|
||||
}
|
||||
|
||||
export function getInitialImageLayerClone(arg: { manager: CanvasManager }): Konva.Layer {
|
||||
const { manager } = arg;
|
||||
|
||||
const initialImage = manager.initialImage;
|
||||
|
||||
const initialImageClone = initialImage.layer.clone();
|
||||
const objectGroupClone = initialImage.group.clone();
|
||||
|
||||
initialImageClone.destroyChildren();
|
||||
initialImageClone.add(objectGroupClone);
|
||||
|
||||
objectGroupClone.opacity(1);
|
||||
objectGroupClone.cache();
|
||||
|
||||
return initialImageClone;
|
||||
}
|
||||
|
||||
export function getCompositeLayerStageClone(arg: { manager: CanvasManager }): Konva.Stage {
|
||||
const { manager } = arg;
|
||||
|
||||
@ -435,6 +452,34 @@ export async function getControlAdapterImage(arg: {
|
||||
return imageDTO;
|
||||
}
|
||||
|
||||
export async function getInitialImage(arg: {
|
||||
manager: CanvasManager;
|
||||
bbox?: Rect;
|
||||
preview?: boolean;
|
||||
}): Promise<ImageDTO> {
|
||||
const { manager, bbox, preview = false } = arg;
|
||||
|
||||
// if (region.imageCache) {
|
||||
// const imageDTO = await this.util.getImageDTO(region.imageCache.name);
|
||||
// if (imageDTO) {
|
||||
// return imageDTO;
|
||||
// }
|
||||
// }
|
||||
|
||||
const layerClone = getInitialImageLayerClone({ manager });
|
||||
const blob = await konvaNodeToBlob(layerClone, bbox);
|
||||
|
||||
if (preview) {
|
||||
previewBlob(blob, 'initial image');
|
||||
}
|
||||
|
||||
layerClone.destroy();
|
||||
|
||||
const imageDTO = await manager.util.uploadImage(blob, 'initial_image.png', 'other', true);
|
||||
// manager.stateApi.onRegionMaskImageCached(ca.id, imageDTO);
|
||||
return imageDTO;
|
||||
}
|
||||
|
||||
export async function getInpaintMaskImage(arg: {
|
||||
manager: CanvasManager;
|
||||
bbox?: Rect;
|
||||
@ -464,7 +509,7 @@ export async function getInpaintMaskImage(arg: {
|
||||
return imageDTO;
|
||||
}
|
||||
|
||||
export async function getImageSourceImage(arg: {
|
||||
export async function getCompositeLayerImage(arg: {
|
||||
manager: CanvasManager;
|
||||
bbox?: Rect;
|
||||
preview?: boolean;
|
||||
|
@ -6,6 +6,7 @@ import { bboxReducers } from 'features/controlLayers/store/bboxReducers';
|
||||
import { compositingReducers } from 'features/controlLayers/store/compositingReducers';
|
||||
import { controlAdaptersReducers } from 'features/controlLayers/store/controlAdaptersReducers';
|
||||
import { documentReducers } from 'features/controlLayers/store/documentReducers';
|
||||
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';
|
||||
@ -30,6 +31,14 @@ 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',
|
||||
@ -141,6 +150,7 @@ export const canvasV2Slice = createSlice({
|
||||
...inpaintMaskReducers,
|
||||
...sessionReducers,
|
||||
...documentReducers,
|
||||
...initialImageReducers,
|
||||
entitySelected: (state, action: PayloadAction<CanvasEntityIdentifier>) => {
|
||||
state.selectedEntityIdentifier = action.payload;
|
||||
},
|
||||
@ -338,6 +348,11 @@ export const {
|
||||
sessionStagingCanceled,
|
||||
sessionNextStagedImageSelected,
|
||||
sessionPrevStagedImageSelected,
|
||||
// Initial image
|
||||
iiRecalled,
|
||||
iiIsEnabledToggled,
|
||||
iiReset,
|
||||
iiImageChanged,
|
||||
} = canvasV2Slice.actions;
|
||||
|
||||
export const selectCanvasV2Slice = (state: RootState) => state.canvasV2;
|
||||
|
@ -28,6 +28,11 @@ export const documentReducers = {
|
||||
if (!state.session.isActive) {
|
||||
state.bbox.rect.width = state.document.rect.width;
|
||||
state.bbox.rect.height = state.document.rect.height;
|
||||
|
||||
if (state.initialImage.imageObject) {
|
||||
state.initialImage.imageObject.width = state.document.rect.width;
|
||||
state.initialImage.imageObject.height = state.document.rect.height;
|
||||
}
|
||||
}
|
||||
},
|
||||
documentHeightChanged: (
|
||||
@ -51,6 +56,11 @@ export const documentReducers = {
|
||||
if (!state.session.isActive) {
|
||||
state.bbox.rect.width = state.document.rect.width;
|
||||
state.bbox.rect.height = state.document.rect.height;
|
||||
|
||||
if (state.initialImage.imageObject) {
|
||||
state.initialImage.imageObject.width = state.document.rect.width;
|
||||
state.initialImage.imageObject.height = state.document.rect.height;
|
||||
}
|
||||
}
|
||||
},
|
||||
documentAspectRatioLockToggled: (state) => {
|
||||
@ -74,6 +84,11 @@ export const documentReducers = {
|
||||
if (!state.session.isActive) {
|
||||
state.bbox.rect.width = state.document.rect.width;
|
||||
state.bbox.rect.height = state.document.rect.height;
|
||||
|
||||
if (state.initialImage.imageObject) {
|
||||
state.initialImage.imageObject.width = state.document.rect.width;
|
||||
state.initialImage.imageObject.height = state.document.rect.height;
|
||||
}
|
||||
}
|
||||
},
|
||||
documentDimensionsSwapped: (state) => {
|
||||
@ -95,6 +110,11 @@ export const documentReducers = {
|
||||
if (!state.session.isActive) {
|
||||
state.bbox.rect.width = state.document.rect.width;
|
||||
state.bbox.rect.height = state.document.rect.height;
|
||||
|
||||
if (state.initialImage.imageObject) {
|
||||
state.initialImage.imageObject.width = state.document.rect.width;
|
||||
state.initialImage.imageObject.height = state.document.rect.height;
|
||||
}
|
||||
}
|
||||
},
|
||||
documentSizeOptimized: (state) => {
|
||||
@ -111,6 +131,11 @@ export const documentReducers = {
|
||||
if (!state.session.isActive) {
|
||||
state.bbox.rect.width = state.document.rect.width;
|
||||
state.bbox.rect.height = state.document.rect.height;
|
||||
|
||||
if (state.initialImage.imageObject) {
|
||||
state.initialImage.imageObject.width = state.document.rect.width;
|
||||
state.initialImage.imageObject.height = state.document.rect.height;
|
||||
}
|
||||
}
|
||||
},
|
||||
} satisfies SliceCaseReducers<CanvasV2State>;
|
||||
|
@ -0,0 +1,38 @@
|
||||
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('initial_image', 'initial_image_object', 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>;
|
@ -668,6 +668,16 @@ const zInpaintMaskEntity = z.object({
|
||||
});
|
||||
export type InpaintMaskEntity = z.infer<typeof zInpaintMaskEntity>;
|
||||
|
||||
const zInitialImageEntity = z.object({
|
||||
id: z.literal('initial_image'),
|
||||
type: z.literal('initial_image'),
|
||||
isEnabled: z.boolean(),
|
||||
bbox: zRect.nullable(),
|
||||
bboxNeedsUpdate: z.boolean(),
|
||||
imageObject: zImageObject.nullable(),
|
||||
});
|
||||
export type InitialImageEntity = z.infer<typeof zInitialImageEntity>;
|
||||
|
||||
const zControlAdapterEntityBase = z.object({
|
||||
id: zId,
|
||||
type: z.literal('control_adapter'),
|
||||
@ -790,7 +800,13 @@ export type BoundingBoxScaleMethod = z.infer<typeof zBoundingBoxScaleMethod>;
|
||||
export const isBoundingBoxScaleMethod = (v: unknown): v is BoundingBoxScaleMethod =>
|
||||
zBoundingBoxScaleMethod.safeParse(v).success;
|
||||
|
||||
export type CanvasEntity = LayerEntity | ControlAdapterEntity | RegionEntity | InpaintMaskEntity | IPAdapterEntity;
|
||||
export type CanvasEntity =
|
||||
| LayerEntity
|
||||
| ControlAdapterEntity
|
||||
| RegionEntity
|
||||
| InpaintMaskEntity
|
||||
| IPAdapterEntity
|
||||
| InitialImageEntity;
|
||||
export type CanvasEntityIdentifier = Pick<CanvasEntity, 'id' | 'type'>;
|
||||
|
||||
export type Size = {
|
||||
@ -822,6 +838,7 @@ export type CanvasV2State = {
|
||||
ipAdapters: { entities: IPAdapterEntity[] };
|
||||
regions: { entities: RegionEntity[] };
|
||||
loras: LoRA[];
|
||||
initialImage: InitialImageEntity;
|
||||
tool: {
|
||||
selected: Tool;
|
||||
selectedBuffer: Tool | null;
|
||||
|
@ -66,6 +66,10 @@ type UpscaleInitialImageDropData = BaseDropData & {
|
||||
actionType: 'SET_UPSCALE_INITIAL_IMAGE';
|
||||
};
|
||||
|
||||
export type InitialImageDropData = BaseDropData & {
|
||||
actionType: 'SET_INITIAL_IMAGE';
|
||||
};
|
||||
|
||||
type NodesImageDropData = BaseDropData & {
|
||||
actionType: 'SET_NODES_IMAGE';
|
||||
context: {
|
||||
@ -101,7 +105,8 @@ export type TypesafeDroppableData =
|
||||
| RGIPAdapterImageDropData
|
||||
| SelectForCompareDropData
|
||||
| RasterLayerImageDropData
|
||||
| UpscaleInitialImageDropData;
|
||||
| UpscaleInitialImageDropData
|
||||
| LayerImageDropData;
|
||||
|
||||
type BaseDragData = {
|
||||
id: string;
|
||||
|
@ -29,6 +29,8 @@ export const isValidDrop = (overData?: TypesafeDroppableData | null, activeData?
|
||||
return payloadType === 'IMAGE_DTO';
|
||||
case 'SELECT_FOR_COMPARE':
|
||||
return payloadType === 'IMAGE_DTO';
|
||||
case 'SET_INITIAL_IMAGE':
|
||||
return payloadType === 'IMAGE_DTO';
|
||||
case 'ADD_TO_BOARD': {
|
||||
// If the board is the same, don't allow the drop
|
||||
|
||||
|
@ -17,8 +17,8 @@ export const addImageToImage = async (
|
||||
): Promise<Invocation<'img_resize' | 'l2i'>> => {
|
||||
denoise.denoising_start = denoising_start;
|
||||
|
||||
const cropBbox = pick(bbox, ['x', 'y', 'width', 'height']);
|
||||
const initialImage = await manager.getImageSourceImage({ bbox: cropBbox });
|
||||
const cropBbox = pick(bbox.rect, ['x', 'y', 'width', 'height']);
|
||||
const initialImage = await manager.getInitialImage({ bbox: cropBbox });
|
||||
|
||||
if (!isEqual(scaledSize, originalSize)) {
|
||||
// Resize the initial image to the scaled size, denoise, then resize back to the original size
|
||||
|
@ -21,8 +21,8 @@ export const addInpaint = async (
|
||||
): Promise<Invocation<'canvas_paste_back'>> => {
|
||||
denoise.denoising_start = denoising_start;
|
||||
|
||||
const cropBbox = pick(bbox, ['x', 'y', 'width', 'height']);
|
||||
const initialImage = await manager.getImageSourceImage({ bbox: cropBbox });
|
||||
const cropBbox = pick(bbox.rect, ['x', 'y', 'width', 'height']);
|
||||
const initialImage = await manager.getInitialImage({ bbox: cropBbox });
|
||||
const maskImage = await manager.getInpaintMaskImage({ bbox: cropBbox });
|
||||
|
||||
if (!isEqual(scaledSize, originalSize)) {
|
||||
|
@ -23,7 +23,7 @@ export const addOutpaint = async (
|
||||
denoise.denoising_start = denoising_start;
|
||||
|
||||
const cropBbox = pick(bbox, ['x', 'y', 'width', 'height']);
|
||||
const initialImage = await manager.getImageSourceImage({ bbox: cropBbox });
|
||||
const initialImage = await manager.getInitialImage({ bbox: cropBbox });
|
||||
const maskImage = await manager.getInpaintMaskImage({ bbox: cropBbox });
|
||||
const infill = getInfill(g, compositing);
|
||||
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { logger } from 'app/logging/logger';
|
||||
import type { RootState } from 'app/store/store';
|
||||
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
|
||||
import { fetchModelConfigWithTypeGuard } from 'features/metadata/util/modelFetchingHelpers';
|
||||
@ -33,9 +34,11 @@ import { isNonRefinerMainModelConfig } from 'services/api/types';
|
||||
import { assert } from 'tsafe';
|
||||
|
||||
import { addRegions } from './addRegions';
|
||||
const log = logger('system');
|
||||
|
||||
export const buildSD1Graph = async (state: RootState, manager: CanvasManager): Promise<Graph> => {
|
||||
const generationMode = manager.getGenerationMode();
|
||||
log.debug({ generationMode }, 'Building SD1/SD2 graph');
|
||||
|
||||
const { bbox, params } = state.canvasV2;
|
||||
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { logger } from 'app/logging/logger';
|
||||
import type { RootState } from 'app/store/store';
|
||||
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
|
||||
import { fetchModelConfigWithTypeGuard } from 'features/metadata/util/modelFetchingHelpers';
|
||||
@ -32,9 +33,11 @@ import { isNonRefinerMainModelConfig } from 'services/api/types';
|
||||
import { assert } from 'tsafe';
|
||||
|
||||
import { addRegions } from './addRegions';
|
||||
const log = logger('system');
|
||||
|
||||
export const buildSDXLGraph = async (state: RootState, manager: CanvasManager): Promise<Graph> => {
|
||||
const generationMode = manager.getGenerationMode();
|
||||
log.debug({ generationMode }, 'Building SDXL graph');
|
||||
|
||||
const { bbox, params } = state.canvasV2;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user