feat(ui): rough out img2img on canvas

This commit is contained in:
psychedelicious 2024-07-11 15:31:21 +10:00
parent 78b4562184
commit 551dd393aa
23 changed files with 468 additions and 16 deletions

View File

@ -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
*/

View File

@ -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>
);
});

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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();
}
}

View File

@ -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 });

View File

@ -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 });
}
}
}

View File

@ -228,6 +228,9 @@ export class CanvasStateApi {
getInpaintMaskState = () => {
return this.getState().inpaintMask;
};
getInitialImageState = () => {
return this.getState().initialImage;
};
getMaskOpacity = () => {
return this.getState().settings.maskOpacity;
};

View File

@ -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;

View File

@ -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;

View File

@ -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>;

View File

@ -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>;

View File

@ -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;

View File

@ -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;

View File

@ -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

View File

@ -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

View File

@ -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)) {

View File

@ -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);

View File

@ -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;

View File

@ -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;