mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
feat(ui): wip raster layers
I meant to split this up into smaller commits and undo some of it, but I committed afterwards and it's tedious to undo.
This commit is contained in:
parent
0e2b328c88
commit
6edd15d68a
@ -1,4 +1,4 @@
|
|||||||
import { Flex, Popover, PopoverBody, PopoverContent, PopoverTrigger, Tooltip } from '@invoke-ai/ui-library';
|
import { Flex, Popover, PopoverBody, PopoverContent, PopoverTrigger } from '@invoke-ai/ui-library';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import IAIColorPicker from 'common/components/IAIColorPicker';
|
import IAIColorPicker from 'common/components/IAIColorPicker';
|
||||||
import { rgbaColorToString } from 'features/canvas/util/colorToString';
|
import { rgbaColorToString } from 'features/canvas/util/colorToString';
|
||||||
@ -20,21 +20,17 @@ export const BrushColorPicker = memo(() => {
|
|||||||
return (
|
return (
|
||||||
<Popover isLazy>
|
<Popover isLazy>
|
||||||
<PopoverTrigger>
|
<PopoverTrigger>
|
||||||
<span>
|
<Flex
|
||||||
<Tooltip label={t('controlLayers.brushColor')}>
|
as="button"
|
||||||
<Flex
|
aria-label={t('controlLayers.brushColor')}
|
||||||
as="button"
|
borderRadius="full"
|
||||||
aria-label={t('controlLayers.brushColor')}
|
borderWidth={1}
|
||||||
borderRadius="full"
|
bg={rgbaColorToString(brushColor)}
|
||||||
borderWidth={1}
|
w={8}
|
||||||
bg={rgbaColorToString(brushColor)}
|
h={8}
|
||||||
w={8}
|
cursor="pointer"
|
||||||
h={8}
|
tabIndex={-1}
|
||||||
cursor="pointer"
|
/>
|
||||||
tabIndex={-1}
|
|
||||||
/>
|
|
||||||
</Tooltip>
|
|
||||||
</span>
|
|
||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
<PopoverContent>
|
<PopoverContent>
|
||||||
<PopoverBody minH={64}>
|
<PopoverBody minH={64}>
|
||||||
|
@ -28,7 +28,7 @@ export const BrushSize = memo(() => {
|
|||||||
[dispatch]
|
[dispatch]
|
||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
<FormControl w="min-content">
|
<FormControl w="min-content" gap={2}>
|
||||||
<FormLabel m={0}>{t('controlLayers.brushSize')}</FormLabel>
|
<FormLabel m={0}>{t('controlLayers.brushSize')}</FormLabel>
|
||||||
<Popover isLazy>
|
<Popover isLazy>
|
||||||
<PopoverTrigger>
|
<PopoverTrigger>
|
||||||
|
@ -1,31 +1,40 @@
|
|||||||
/* eslint-disable i18next/no-literal-string */
|
/* eslint-disable i18next/no-literal-string */
|
||||||
import { Flex } from '@invoke-ai/ui-library';
|
import { Flex } from '@invoke-ai/ui-library';
|
||||||
|
import { useStore } from '@nanostores/react';
|
||||||
import { BrushColorPicker } from 'features/controlLayers/components/BrushColorPicker';
|
import { BrushColorPicker } from 'features/controlLayers/components/BrushColorPicker';
|
||||||
import { BrushSize } from 'features/controlLayers/components/BrushSize';
|
import { BrushSize } from 'features/controlLayers/components/BrushSize';
|
||||||
import ControlLayersSettingsPopover from 'features/controlLayers/components/ControlLayersSettingsPopover';
|
import ControlLayersSettingsPopover from 'features/controlLayers/components/ControlLayersSettingsPopover';
|
||||||
import { ToolChooser } from 'features/controlLayers/components/ToolChooser';
|
import { ToolChooser } from 'features/controlLayers/components/ToolChooser';
|
||||||
import { UndoRedoButtonGroup } from 'features/controlLayers/components/UndoRedoButtonGroup';
|
import { UndoRedoButtonGroup } from 'features/controlLayers/components/UndoRedoButtonGroup';
|
||||||
|
import { $tool } from 'features/controlLayers/store/controlLayersSlice';
|
||||||
import { ToggleProgressButton } from 'features/gallery/components/ImageViewer/ToggleProgressButton';
|
import { ToggleProgressButton } from 'features/gallery/components/ImageViewer/ToggleProgressButton';
|
||||||
import { ViewerToggleMenu } from 'features/gallery/components/ImageViewer/ViewerToggleMenu';
|
import { ViewerToggleMenu } from 'features/gallery/components/ImageViewer/ViewerToggleMenu';
|
||||||
import { memo } from 'react';
|
import { memo, useMemo } from 'react';
|
||||||
|
|
||||||
export const ControlLayersToolbar = memo(() => {
|
export const ControlLayersToolbar = memo(() => {
|
||||||
|
const tool = useStore($tool);
|
||||||
|
const withBrushSize = useMemo(() => {
|
||||||
|
return tool === 'brush' || tool === 'eraser';
|
||||||
|
}, [tool]);
|
||||||
|
const withBrushColor = useMemo(() => {
|
||||||
|
return tool === 'brush';
|
||||||
|
}, [tool]);
|
||||||
return (
|
return (
|
||||||
<Flex w="full" gap={2}>
|
<Flex w="full" gap={2}>
|
||||||
<Flex flex={1} justifyContent="center">
|
<Flex flex={1} justifyContent="center">
|
||||||
<Flex gap={2} marginInlineEnd="auto">
|
<Flex gap={2} marginInlineEnd="auto">
|
||||||
<ToggleProgressButton />
|
<ToggleProgressButton />
|
||||||
|
<ToolChooser />
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Flex flex={1} gap={2} justifyContent="center">
|
<Flex flex={1} gap={2} justifyContent="center" alignItems="center">
|
||||||
<BrushSize />
|
{withBrushSize && <BrushSize />}
|
||||||
<BrushColorPicker />
|
{withBrushColor && <BrushColorPicker />}
|
||||||
<ToolChooser />
|
|
||||||
<UndoRedoButtonGroup />
|
|
||||||
<ControlLayersSettingsPopover />
|
|
||||||
</Flex>
|
</Flex>
|
||||||
<Flex flex={1} justifyContent="center">
|
<Flex flex={1} justifyContent="center">
|
||||||
<Flex gap={2} marginInlineStart="auto">
|
<Flex gap={2} marginInlineStart="auto">
|
||||||
|
<UndoRedoButtonGroup />
|
||||||
|
<ControlLayersSettingsPopover />
|
||||||
<ViewerToggleMenu />
|
<ViewerToggleMenu />
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
@ -35,13 +35,15 @@ export const RASTER_LAYER_OBJECT_GROUP_NAME = 'raster_layer.object_group';
|
|||||||
export const RASTER_LAYER_BRUSH_LINE_NAME = 'raster_layer.brush_line';
|
export const RASTER_LAYER_BRUSH_LINE_NAME = 'raster_layer.brush_line';
|
||||||
export const RASTER_LAYER_ERASER_LINE_NAME = 'raster_layer.eraser_line';
|
export const RASTER_LAYER_ERASER_LINE_NAME = 'raster_layer.eraser_line';
|
||||||
export const RASTER_LAYER_RECT_SHAPE_NAME = 'raster_layer.rect_shape';
|
export const RASTER_LAYER_RECT_SHAPE_NAME = 'raster_layer.rect_shape';
|
||||||
|
export const RASTER_LAYER_IMAGE_NAME = 'raster_layer.image';
|
||||||
|
|
||||||
// Getters for non-singleton layer and object IDs
|
// Getters for non-singleton layer and object IDs
|
||||||
export const getRGLayerId = (layerId: string) => `${RG_LAYER_NAME}_${layerId}`;
|
export const getRGLayerId = (layerId: string) => `${RG_LAYER_NAME}_${layerId}`;
|
||||||
export const getRasterLayerId = (layerId: string) => `${RASTER_LAYER_NAME}_${layerId}`;
|
export const getRasterLayerId = (layerId: string) => `${RASTER_LAYER_NAME}_${layerId}`;
|
||||||
export const getBrushLineId = (layerId: string, lineId: string) => `${layerId}.brush_line_${lineId}`;
|
export const getBrushLineId = (layerId: string, lineId: string) => `${layerId}.brush_line_${lineId}`;
|
||||||
export const getEraserLineId = (layerId: string, lineId: string) => `${layerId}.eraser_line_${lineId}`;
|
export const getEraserLineId = (layerId: string, lineId: string) => `${layerId}.eraser_line_${lineId}`;
|
||||||
export const getRectId = (layerId: string, lineId: string) => `${layerId}.rect_${lineId}`;
|
export const getRectShapeId = (layerId: string, lineId: string) => `${layerId}.rect_${lineId}`;
|
||||||
|
export const getImageObjectId = (layerId: string, imageName: string) => `${layerId}.image_${imageName}`;
|
||||||
export const getObjectGroupId = (layerId: string, groupId: string) => `${layerId}.objectGroup_${groupId}`;
|
export const getObjectGroupId = (layerId: string, groupId: string) => `${layerId}.objectGroup_${groupId}`;
|
||||||
export const getLayerBboxId = (layerId: string) => `${layerId}.bbox`;
|
export const getLayerBboxId = (layerId: string) => `${layerId}.bbox`;
|
||||||
export const getCALayerId = (layerId: string) => `control_adapter_layer_${layerId}`;
|
export const getCALayerId = (layerId: string) => `control_adapter_layer_${layerId}`;
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
import { rgbaColorToString } from 'features/canvas/util/colorToString';
|
import { rgbaColorToString } from 'features/canvas/util/colorToString';
|
||||||
import { getObjectGroupId } from 'features/controlLayers/konva/naming';
|
import { getObjectGroupId } from 'features/controlLayers/konva/naming';
|
||||||
import type { BrushLine, EraserLine, RectShape } from 'features/controlLayers/store/types';
|
import type { BrushLine, EraserLine, ImageObject, RectShape } from 'features/controlLayers/store/types';
|
||||||
import { DEFAULT_RGBA_COLOR } from 'features/controlLayers/store/types';
|
import { DEFAULT_RGBA_COLOR } from 'features/controlLayers/store/types';
|
||||||
import Konva from 'konva';
|
import Konva from 'konva';
|
||||||
|
import { getImageDTO } from 'services/api/endpoints/images';
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -80,6 +81,34 @@ export const createRectShape = (rectShape: RectShape, layerObjectGroup: Konva.Gr
|
|||||||
return konvaRect;
|
return konvaRect;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const createImageObject = async (
|
||||||
|
imageObject: ImageObject,
|
||||||
|
layerObjectGroup: Konva.Group,
|
||||||
|
name: string
|
||||||
|
): Promise<Konva.Image | null> => {
|
||||||
|
const imageDTO = await getImageDTO(imageObject.image.name);
|
||||||
|
if (!imageDTO) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const imageEl = new Image();
|
||||||
|
imageEl.onload = () => {
|
||||||
|
const konvaImage = new Konva.Image({
|
||||||
|
id: imageObject.id,
|
||||||
|
name,
|
||||||
|
listening: false,
|
||||||
|
image: imageEl,
|
||||||
|
});
|
||||||
|
layerObjectGroup.add(konvaImage);
|
||||||
|
resolve(konvaImage);
|
||||||
|
};
|
||||||
|
imageEl.onerror = () => {
|
||||||
|
resolve(null);
|
||||||
|
};
|
||||||
|
imageEl.id = imageObject.id;
|
||||||
|
imageEl.src = imageDTO.image_url;
|
||||||
|
});
|
||||||
|
};
|
||||||
/**
|
/**
|
||||||
* Creates a konva group for a layer's objects.
|
* Creates a konva group for a layer's objects.
|
||||||
* @param konvaLayer The konva layer to add the object group to
|
* @param konvaLayer The konva layer to add the object group to
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
RASTER_LAYER_BRUSH_LINE_NAME,
|
RASTER_LAYER_BRUSH_LINE_NAME,
|
||||||
RASTER_LAYER_ERASER_LINE_NAME,
|
RASTER_LAYER_ERASER_LINE_NAME,
|
||||||
|
RASTER_LAYER_IMAGE_NAME,
|
||||||
RASTER_LAYER_NAME,
|
RASTER_LAYER_NAME,
|
||||||
RASTER_LAYER_OBJECT_GROUP_NAME,
|
RASTER_LAYER_OBJECT_GROUP_NAME,
|
||||||
RASTER_LAYER_RECT_SHAPE_NAME,
|
RASTER_LAYER_RECT_SHAPE_NAME,
|
||||||
@ -8,12 +9,14 @@ import {
|
|||||||
import {
|
import {
|
||||||
createBrushLine,
|
createBrushLine,
|
||||||
createEraserLine,
|
createEraserLine,
|
||||||
|
createImageObject,
|
||||||
createObjectGroup,
|
createObjectGroup,
|
||||||
createRectShape,
|
createRectShape,
|
||||||
} from 'features/controlLayers/konva/renderers/objects';
|
} from 'features/controlLayers/konva/renderers/objects';
|
||||||
import { getScaledFlooredCursorPosition, mapId, selectRasterObjects } from 'features/controlLayers/konva/util';
|
import { getScaledFlooredCursorPosition, mapId, selectRasterObjects } from 'features/controlLayers/konva/util';
|
||||||
import type { RasterLayer, Tool } from 'features/controlLayers/store/types';
|
import type { RasterLayer, Tool } from 'features/controlLayers/store/types';
|
||||||
import Konva from 'konva';
|
import Konva from 'konva';
|
||||||
|
import { assert } from 'tsafe';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Logic for creating and rendering raster layers.
|
* Logic for creating and rendering raster layers.
|
||||||
@ -76,12 +79,12 @@ const createRasterLayer = (
|
|||||||
* @param tool The current tool
|
* @param tool The current tool
|
||||||
* @param onLayerPosChanged Callback for when the layer's position changes
|
* @param onLayerPosChanged Callback for when the layer's position changes
|
||||||
*/
|
*/
|
||||||
export const renderRasterLayer = (
|
export const renderRasterLayer = async (
|
||||||
stage: Konva.Stage,
|
stage: Konva.Stage,
|
||||||
layerState: RasterLayer,
|
layerState: RasterLayer,
|
||||||
tool: Tool,
|
tool: Tool,
|
||||||
onLayerPosChanged?: (layerId: string, x: number, y: number) => void
|
onLayerPosChanged?: (layerId: string, x: number, y: number) => void
|
||||||
): void => {
|
) => {
|
||||||
const konvaLayer =
|
const konvaLayer =
|
||||||
stage.findOne<Konva.Layer>(`#${layerState.id}`) ?? createRasterLayer(stage, layerState, onLayerPosChanged);
|
stage.findOne<Konva.Layer>(`#${layerState.id}`) ?? createRasterLayer(stage, layerState, onLayerPosChanged);
|
||||||
|
|
||||||
@ -106,26 +109,38 @@ export const renderRasterLayer = (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const obj of layerState.objects) {
|
for (let i = 0; i < layerState.objects.length; i++) {
|
||||||
|
const obj = layerState.objects[i];
|
||||||
|
assert(obj);
|
||||||
|
const zIndex = layerState.objects.length - i;
|
||||||
if (obj.type === 'brush_line') {
|
if (obj.type === 'brush_line') {
|
||||||
const konvaBrushLine =
|
const konvaBrushLine =
|
||||||
stage.findOne<Konva.Line>(`#${obj.id}`) ?? createBrushLine(obj, konvaObjectGroup, RASTER_LAYER_BRUSH_LINE_NAME);
|
konvaObjectGroup.findOne<Konva.Line>(`#${obj.id}`) ??
|
||||||
|
createBrushLine(obj, konvaObjectGroup, RASTER_LAYER_BRUSH_LINE_NAME);
|
||||||
// Only update the points if they have changed.
|
// Only update the points if they have changed.
|
||||||
if (konvaBrushLine.points().length !== obj.points.length) {
|
if (konvaBrushLine.points().length !== obj.points.length) {
|
||||||
konvaBrushLine.points(obj.points);
|
konvaBrushLine.points(obj.points);
|
||||||
}
|
}
|
||||||
|
konvaBrushLine.zIndex(zIndex);
|
||||||
} else if (obj.type === 'eraser_line') {
|
} else if (obj.type === 'eraser_line') {
|
||||||
const konvaEraserLine =
|
const konvaEraserLine =
|
||||||
stage.findOne<Konva.Line>(`#${obj.id}`) ??
|
konvaObjectGroup.findOne<Konva.Line>(`#${obj.id}`) ??
|
||||||
createEraserLine(obj, konvaObjectGroup, RASTER_LAYER_ERASER_LINE_NAME);
|
createEraserLine(obj, konvaObjectGroup, RASTER_LAYER_ERASER_LINE_NAME);
|
||||||
// Only update the points if they have changed.
|
// Only update the points if they have changed.
|
||||||
if (konvaEraserLine.points().length !== obj.points.length) {
|
if (konvaEraserLine.points().length !== obj.points.length) {
|
||||||
konvaEraserLine.points(obj.points);
|
konvaEraserLine.points(obj.points);
|
||||||
}
|
}
|
||||||
|
konvaEraserLine.zIndex(zIndex);
|
||||||
} else if (obj.type === 'rect_shape') {
|
} else if (obj.type === 'rect_shape') {
|
||||||
if (!stage.findOne<Konva.Rect>(`#${obj.id}`)) {
|
const konvaRect =
|
||||||
|
konvaObjectGroup.findOne<Konva.Rect>(`#${obj.id}`) ??
|
||||||
createRectShape(obj, konvaObjectGroup, RASTER_LAYER_RECT_SHAPE_NAME);
|
createRectShape(obj, konvaObjectGroup, RASTER_LAYER_RECT_SHAPE_NAME);
|
||||||
}
|
konvaRect.zIndex(zIndex);
|
||||||
|
} else if (obj.type === 'image') {
|
||||||
|
const konvaImage =
|
||||||
|
konvaObjectGroup.findOne<Konva.Image>(`#${obj.id}`) ??
|
||||||
|
(await createImageObject(obj, konvaObjectGroup, RASTER_LAYER_IMAGE_NAME));
|
||||||
|
konvaImage?.zIndex(zIndex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@ import {
|
|||||||
INITIAL_IMAGE_LAYER_NAME,
|
INITIAL_IMAGE_LAYER_NAME,
|
||||||
RASTER_LAYER_BRUSH_LINE_NAME,
|
RASTER_LAYER_BRUSH_LINE_NAME,
|
||||||
RASTER_LAYER_ERASER_LINE_NAME,
|
RASTER_LAYER_ERASER_LINE_NAME,
|
||||||
|
RASTER_LAYER_IMAGE_NAME,
|
||||||
RASTER_LAYER_NAME,
|
RASTER_LAYER_NAME,
|
||||||
RASTER_LAYER_RECT_SHAPE_NAME,
|
RASTER_LAYER_RECT_SHAPE_NAME,
|
||||||
RG_LAYER_BRUSH_LINE_NAME,
|
RG_LAYER_BRUSH_LINE_NAME,
|
||||||
@ -115,5 +116,6 @@ export const selectVectorMaskObjects = (node: Konva.Node): boolean =>
|
|||||||
export const selectRasterObjects = (node: Konva.Node): boolean =>
|
export const selectRasterObjects = (node: Konva.Node): boolean =>
|
||||||
node.name() === RASTER_LAYER_BRUSH_LINE_NAME ||
|
node.name() === RASTER_LAYER_BRUSH_LINE_NAME ||
|
||||||
node.name() === RASTER_LAYER_ERASER_LINE_NAME ||
|
node.name() === RASTER_LAYER_ERASER_LINE_NAME ||
|
||||||
node.name() === RASTER_LAYER_RECT_SHAPE_NAME;
|
node.name() === RASTER_LAYER_RECT_SHAPE_NAME ||
|
||||||
|
node.name() === RASTER_LAYER_IMAGE_NAME;
|
||||||
//#endregion
|
//#endregion
|
||||||
|
@ -11,7 +11,7 @@ import {
|
|||||||
getImageObjectId,
|
getImageObjectId,
|
||||||
getIPALayerId,
|
getIPALayerId,
|
||||||
getRasterLayerId,
|
getRasterLayerId,
|
||||||
getRectId,
|
getRectShapeId,
|
||||||
getRGLayerId,
|
getRGLayerId,
|
||||||
INITIAL_IMAGE_LAYER_ID,
|
INITIAL_IMAGE_LAYER_ID,
|
||||||
} from 'features/controlLayers/konva/naming';
|
} from 'features/controlLayers/konva/naming';
|
||||||
|
Loading…
Reference in New Issue
Block a user