feat(ui): support image objects on raster layers

Just the UI and internal state, not rendering yet.
This commit is contained in:
psychedelicious 2024-06-06 11:06:49 +10:00
parent cefd9a027c
commit 5c0676bcc2
7 changed files with 68 additions and 3 deletions

View File

@ -10,6 +10,7 @@ import {
import { import {
caLayerImageChanged, caLayerImageChanged,
iiLayerImageChanged, iiLayerImageChanged,
imageAdded,
ipaLayerImageChanged, ipaLayerImageChanged,
rgLayerIPAdapterImageChanged, rgLayerIPAdapterImageChanged,
} from 'features/controlLayers/store/controlLayersSlice'; } from 'features/controlLayers/store/controlLayersSlice';
@ -161,6 +162,24 @@ export const addImageDroppedListener = (startAppListening: AppStartListening) =>
return; return;
} }
/**
* Image dropped on Raster layer
*/
if (
overData.actionType === 'ADD_RASTER_LAYER_IMAGE' &&
activeData.payloadType === 'IMAGE_DTO' &&
activeData.payload.imageDTO
) {
const { layerId } = overData.context;
dispatch(
imageAdded({
layerId,
imageDTO: activeData.payload.imageDTO,
})
);
return;
}
/** /**
* Image dropped on Canvas * Image dropped on Canvas
*/ */

View File

@ -11,6 +11,7 @@ type Props = PropsWithChildren<{
export const LayerWrapper = memo(({ onClick, borderColor, children }: Props) => { export const LayerWrapper = memo(({ onClick, borderColor, children }: Props) => {
return ( return (
<Flex <Flex
position="relative"
gap={2} gap={2}
onClick={onClick} onClick={onClick}
bg={borderColor} bg={borderColor}

View File

@ -1,5 +1,6 @@
import { Flex, Spacer, useDisclosure } from '@invoke-ai/ui-library'; import { Flex, Spacer, useDisclosure } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAIDroppable from 'common/components/IAIDroppable';
import { LayerDeleteButton } from 'features/controlLayers/components/LayerCommon/LayerDeleteButton'; import { LayerDeleteButton } from 'features/controlLayers/components/LayerCommon/LayerDeleteButton';
import { LayerMenu } from 'features/controlLayers/components/LayerCommon/LayerMenu'; import { LayerMenu } from 'features/controlLayers/components/LayerCommon/LayerMenu';
import { LayerOpacity } from 'features/controlLayers/components/LayerCommon/LayerOpacity'; import { LayerOpacity } from 'features/controlLayers/components/LayerCommon/LayerOpacity';
@ -8,7 +9,8 @@ import { LayerIsEnabledToggle } from 'features/controlLayers/components/LayerCom
import { LayerWrapper } from 'features/controlLayers/components/LayerCommon/LayerWrapper'; import { LayerWrapper } from 'features/controlLayers/components/LayerCommon/LayerWrapper';
import { layerSelected, selectLayerOrThrow } from 'features/controlLayers/store/controlLayersSlice'; import { layerSelected, selectLayerOrThrow } from 'features/controlLayers/store/controlLayersSlice';
import { isRasterLayer } from 'features/controlLayers/store/types'; import { isRasterLayer } from 'features/controlLayers/store/types';
import { memo, useCallback } from 'react'; import type { RasterLayerImageDropData } from 'features/dnd/types';
import { memo, useCallback, useMemo } from 'react';
type Props = { type Props = {
layerId: string; layerId: string;
@ -24,6 +26,15 @@ export const RasterLayer = memo(({ layerId }: Props) => {
}, [dispatch, layerId]); }, [dispatch, layerId]);
const { isOpen, onToggle } = useDisclosure({ defaultIsOpen: true }); const { isOpen, onToggle } = useDisclosure({ defaultIsOpen: true });
const droppableData = useMemo(() => {
const _droppableData: RasterLayerImageDropData = {
id: layerId,
actionType: 'ADD_RASTER_LAYER_IMAGE',
context: { layerId },
};
return _droppableData;
}, [layerId]);
return ( return (
<LayerWrapper onClick={onClick} borderColor={isSelected ? 'base.400' : 'base.800'}> <LayerWrapper onClick={onClick} borderColor={isSelected ? 'base.400' : 'base.800'}>
<Flex gap={3} alignItems="center" p={3} cursor="pointer" onDoubleClick={onToggle}> <Flex gap={3} alignItems="center" p={3} cursor="pointer" onDoubleClick={onToggle}>
@ -39,6 +50,7 @@ export const RasterLayer = memo(({ layerId }: Props) => {
PLACEHOLDER PLACEHOLDER
</Flex> </Flex>
)} )}
<IAIDroppable data={droppableData} />
</LayerWrapper> </LayerWrapper>
); );
}); });

View File

@ -8,6 +8,7 @@ import {
getBrushLineId, getBrushLineId,
getCALayerId, getCALayerId,
getEraserLineId, getEraserLineId,
getImageObjectId,
getIPALayerId, getIPALayerId,
getRasterLayerId, getRasterLayerId,
getRectId, getRectId,
@ -48,6 +49,7 @@ import { v4 as uuidv4 } from 'uuid';
import type { import type {
AddBrushLineArg, AddBrushLineArg,
AddEraserLineArg, AddEraserLineArg,
AddImageObjectArg,
AddPointToLineArg, AddPointToLineArg,
AddRectShapeArg, AddRectShapeArg,
ControlAdapterLayer, ControlAdapterLayer,
@ -715,7 +717,7 @@ export const controlLayersSlice = createSlice({
return; return;
} }
const layer = selectLayerOrThrow(state, layerId, isRGOrRasterlayer); const layer = selectLayerOrThrow(state, layerId, isRGOrRasterlayer);
const id = getRectId(layer.id, rectUuid); const id = getRectShapeId(layer.id, rectUuid);
layer.objects.push({ layer.objects.push({
type: 'rect_shape', type: 'rect_shape',
id, id,
@ -732,6 +734,25 @@ export const controlLayersSlice = createSlice({
}, },
prepare: (payload: AddRectShapeArg) => ({ payload: { ...payload, rectUuid: uuidv4() } }), prepare: (payload: AddRectShapeArg) => ({ payload: { ...payload, rectUuid: uuidv4() } }),
}, },
imageAdded: {
reducer: (state, action: PayloadAction<AddImageObjectArg & { imageUuid: string }>) => {
const { layerId, imageUuid, imageDTO } = action.payload;
const layer = selectLayerOrThrow(state, layerId, isRasterLayer);
const id = getImageObjectId(layer.id, imageUuid);
const { width, height, image_name: name } = imageDTO;
layer.objects.push({
type: 'image',
id,
x: 0,
y: 0,
width,
height,
image: { width, height, name },
});
layer.bboxNeedsUpdate = true;
},
prepare: (payload: AddImageObjectArg) => ({ payload: { ...payload, imageUuid: uuidv4() } }),
},
//#endregion //#endregion
//#region Globals //#region Globals
@ -897,6 +918,7 @@ export const {
eraserLineAdded, eraserLineAdded,
linePointsAdded, linePointsAdded,
rectAdded, rectAdded,
imageAdded,
rgLayerMaskImageUploaded, rgLayerMaskImageUploaded,
rgLayerAutoNegativeChanged, rgLayerAutoNegativeChanged,
rgLayerIPAdapterAdded, rgLayerIPAdapterAdded,

View File

@ -20,6 +20,7 @@ import {
zParameterStrength, zParameterStrength,
} from 'features/parameters/types/parameterSchemas'; } from 'features/parameters/types/parameterSchemas';
import type { IRect } from 'konva/lib/types'; import type { IRect } from 'konva/lib/types';
import type { ImageDTO } from 'services/api/types';
import { z } from 'zod'; import { z } from 'zod';
const zTool = z.enum(['brush', 'eraser', 'move', 'rect']); const zTool = z.enum(['brush', 'eraser', 'move', 'rect']);
@ -273,7 +274,8 @@ export type ControlLayersState = {
export type AddEraserLineArg = { layerId: string; points: [number, number, number, number] }; export type AddEraserLineArg = { layerId: string; points: [number, number, number, number] };
export type AddBrushLineArg = AddEraserLineArg & { color: RgbaColor }; export type AddBrushLineArg = AddEraserLineArg & { color: RgbaColor };
export type AddPointToLineArg = { layerId: string; point: [number, number] }; export type AddPointToLineArg = { layerId: string; point: [number, number] };
export type AddRectShapeArg = { layerId: string; rect: IRect; color: RgbaColor }; //#region Type guards export type AddRectShapeArg = { layerId: string; rect: IRect; color: RgbaColor };
export type AddImageObjectArg = { layerId: string; imageDTO: ImageDTO };
//#region Type guards //#region Type guards
export const isLine = (obj: AnyLayerObject): obj is BrushLine | EraserLine => { export const isLine = (obj: AnyLayerObject): obj is BrushLine | EraserLine => {

View File

@ -58,6 +58,13 @@ export type IILayerImageDropData = BaseDropData & {
}; };
}; };
export type RasterLayerImageDropData = BaseDropData & {
actionType: 'ADD_RASTER_LAYER_IMAGE';
context: {
layerId: string;
};
};
export type CanvasInitialImageDropData = BaseDropData & { export type CanvasInitialImageDropData = BaseDropData & {
actionType: 'SET_CANVAS_INITIAL_IMAGE'; actionType: 'SET_CANVAS_INITIAL_IMAGE';
}; };

View File

@ -33,6 +33,8 @@ export const isValidDrop = (overData?: TypesafeDroppableData | null, activeData?
return payloadType === 'IMAGE_DTO'; return payloadType === 'IMAGE_DTO';
case 'SELECT_FOR_COMPARE': case 'SELECT_FOR_COMPARE':
return payloadType === 'IMAGE_DTO'; return payloadType === 'IMAGE_DTO';
case 'ADD_RASTER_LAYER_IMAGE':
return payloadType === 'IMAGE_DTO';
case 'ADD_TO_BOARD': { case 'ADD_TO_BOARD': {
// If the board is the same, don't allow the drop // If the board is the same, don't allow the drop