refactor(ui): split up canvas entity renderers, temp disable preview

This commit is contained in:
psychedelicious 2024-06-17 16:49:07 +10:00
parent 15ad4e3f5e
commit 007e2553a8
8 changed files with 109 additions and 33 deletions

View File

@ -5,7 +5,14 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { HeadsUpDisplay } from 'features/controlLayers/components/HeadsUpDisplay'; import { HeadsUpDisplay } from 'features/controlLayers/components/HeadsUpDisplay';
import { setStageEventHandlers } from 'features/controlLayers/konva/events'; import { setStageEventHandlers } from 'features/controlLayers/konva/events';
import { renderBackgroundLayer } from 'features/controlLayers/konva/renderers/background'; import { renderBackgroundLayer } from 'features/controlLayers/konva/renderers/background';
import { debouncedRenderers, renderers as normalRenderers } from 'features/controlLayers/konva/renderers/layers'; import { renderControlAdapters } from 'features/controlLayers/konva/renderers/caLayer';
import {
arrangeEntities,
debouncedRenderers,
renderers as normalRenderers,
} from 'features/controlLayers/konva/renderers/layers';
import { renderLayers } from 'features/controlLayers/konva/renderers/rasterLayer';
import { renderRegions } from 'features/controlLayers/konva/renderers/rgLayer';
import { import {
$bbox, $bbox,
$currentFill, $currentFill,
@ -352,18 +359,22 @@ const useStageRenderer = (stage: Konva.Stage, container: HTMLDivElement | null,
useLayoutEffect(() => { useLayoutEffect(() => {
log.trace('Rendering layers'); log.trace('Rendering layers');
renderers.renderLayers( renderLayers(stage, layers, tool.selected, onPosChanged);
stage, }, [layers, onPosChanged, stage, tool.selected]);
layers,
controlAdapters, useLayoutEffect(() => {
regions, log.trace('Rendering regions');
maskOpacity, renderRegions(stage, regions, maskOpacity, tool.selected, selectedEntity, onPosChanged);
tool.selected, }, [maskOpacity, onPosChanged, regions, selectedEntity, stage, tool.selected]);
selectedEntity,
getImageDTO, useLayoutEffect(() => {
onPosChanged log.trace('Rendering layers');
); renderControlAdapters(stage, controlAdapters, getImageDTO);
}, [controlAdapters, layers, maskOpacity, onPosChanged, regions, renderers, selectedEntity, stage, tool.selected]); }, [controlAdapters, stage]);
useLayoutEffect(() => {
arrangeEntities(stage, layers, controlAdapters, regions);
}, [layers, controlAdapters, regions, stage]);
// useLayoutEffect(() => { // useLayoutEffect(() => {
// if (asPreview) { // if (asPreview) {

View File

@ -43,6 +43,8 @@ export const RASTER_LAYER_IMAGE_NAME = 'raster_layer.image';
export const INPAINT_MASK_LAYER_NAME = 'inpaint_mask_layer'; export const INPAINT_MASK_LAYER_NAME = 'inpaint_mask_layer';
export const BACKGROUND_LAYER_ID = 'background_layer';
// Getters for non-singleton layer and object IDs // Getters for non-singleton layer and object IDs
export const getRGId = (entityId: string) => `${RG_LAYER_NAME}_${entityId}`; export const getRGId = (entityId: string) => `${RG_LAYER_NAME}_${entityId}`;
export const getLayerId = (entityId: string) => `${RASTER_LAYER_NAME}_${entityId}`; export const getLayerId = (entityId: string) => `${RASTER_LAYER_NAME}_${entityId}`;

View File

@ -1,4 +1,5 @@
import { getArbitraryBaseColor } from '@invoke-ai/ui-library'; import { getArbitraryBaseColor } from '@invoke-ai/ui-library';
import { BACKGROUND_LAYER_ID } from 'features/controlLayers/konva/naming';
import Konva from 'konva'; import Konva from 'konva';
const baseGridLineColor = getArbitraryBaseColor(27); const baseGridLineColor = getArbitraryBaseColor(27);
@ -23,13 +24,13 @@ const getGridSpacing = (scale: number): number => {
return 256; return 256;
}; };
const getBackgroundLayer = (stage: Konva.Stage): Konva.Layer => { export const getBackgroundLayer = (stage: Konva.Stage): Konva.Layer => {
let background = stage.findOne<Konva.Layer>('#background'); let background = stage.findOne<Konva.Layer>(`#${BACKGROUND_LAYER_ID}`);
if (background) { if (background) {
return background; return background;
} }
background = new Konva.Layer({ id: 'background' }); background = new Konva.Layer({ id: BACKGROUND_LAYER_ID });
stage.add(background); stage.add(background);
return background; return background;
}; };

View File

@ -92,10 +92,9 @@ const updateCALayerImageSource = async (
const updateCALayerImageAttrs = (stage: Konva.Stage, konvaImage: Konva.Image, ca: ControlAdapterData): void => { const updateCALayerImageAttrs = (stage: Konva.Stage, konvaImage: Konva.Image, ca: ControlAdapterData): void => {
let needsCache = false; let needsCache = false;
// Konva erroneously reports NaN for width and height when the stage is hidden. This causes errors when caching, // TODO(psyche): `node.filters()` returns null if no filters; report upstream
// but it doesn't seem to break anything. const filters = konvaImage.filters() ?? [];
// TODO(psyche): Investigate and report upstream. const filter = filters[0] ?? null;
const filter = konvaImage.filters()[0] ?? null;
const filterNeedsUpdate = (filter === null && ca.filter !== 'none') || (filter && filter.name !== ca.filter); const filterNeedsUpdate = (filter === null && ca.filter !== 'none') || (filter && filter.name !== ca.filter);
if ( if (
konvaImage.x() !== ca.x || konvaImage.x() !== ca.x ||
@ -130,13 +129,9 @@ const updateCALayerImageAttrs = (stage: Konva.Stage, konvaImage: Konva.Image, ca
export const renderCALayer = ( export const renderCALayer = (
stage: Konva.Stage, stage: Konva.Stage,
ca: ControlAdapterData, ca: ControlAdapterData,
zIndex: number,
getImageDTO: (imageName: string) => Promise<ImageDTO | null> getImageDTO: (imageName: string) => Promise<ImageDTO | null>
): void => { ): void => {
const konvaLayer = stage.findOne<Konva.Layer>(`#${ca.id}`) ?? createCALayer(stage, ca); const konvaLayer = stage.findOne<Konva.Layer>(`#${ca.id}`) ?? createCALayer(stage, ca);
konvaLayer.zIndex(zIndex);
const konvaImage = konvaLayer.findOne<Konva.Image>(`.${CA_LAYER_IMAGE_NAME}`); const konvaImage = konvaLayer.findOne<Konva.Image>(`.${CA_LAYER_IMAGE_NAME}`);
const canvasImageSource = konvaImage?.image(); const canvasImageSource = konvaImage?.image();
@ -159,3 +154,19 @@ export const renderCALayer = (
updateCALayerImageAttrs(stage, konvaImage, ca); updateCALayerImageAttrs(stage, konvaImage, ca);
} }
}; };
export const renderControlAdapters = (
stage: Konva.Stage,
controlAdapters: ControlAdapterData[],
getImageDTO: (imageName: string) => Promise<ImageDTO | null>
): void => {
// Destroy nonexistent layers
for (const konvaLayer of stage.find<Konva.Layer>(`.${CA_LAYER_NAME}`)) {
if (!controlAdapters.find((ca) => ca.id === konvaLayer.id())) {
konvaLayer.destroy();
}
}
for (const ca of controlAdapters) {
renderCALayer(stage, ca, getImageDTO);
}
};

View File

@ -1,5 +1,5 @@
import { DEBOUNCE_MS } from 'features/controlLayers/konva/constants'; import { DEBOUNCE_MS } from 'features/controlLayers/konva/constants';
import { PREVIEW_LAYER_ID } from 'features/controlLayers/konva/naming'; import { BACKGROUND_LAYER_ID, PREVIEW_LAYER_ID } from 'features/controlLayers/konva/naming';
import { updateBboxes } from 'features/controlLayers/konva/renderers/bbox'; import { updateBboxes } from 'features/controlLayers/konva/renderers/bbox';
import { renderCALayer } from 'features/controlLayers/konva/renderers/caLayer'; import { renderCALayer } from 'features/controlLayers/konva/renderers/caLayer';
import { renderBboxPreview, renderToolPreview } from 'features/controlLayers/konva/renderers/previewLayer'; import { renderBboxPreview, renderToolPreview } from 'features/controlLayers/konva/renderers/previewLayer';
@ -93,3 +93,23 @@ const getDebouncedRenderers = (ms = DEBOUNCE_MS): typeof renderers => ({
* All the renderers for the Konva stage, debounced. * All the renderers for the Konva stage, debounced.
*/ */
export const debouncedRenderers: typeof renderers = getDebouncedRenderers(); export const debouncedRenderers: typeof renderers = getDebouncedRenderers();
export const arrangeEntities = (
stage: Konva.Stage,
layers: LayerData[],
controlAdapters: ControlAdapterData[],
regions: RegionalGuidanceData[]
): void => {
let zIndex = 0;
stage.findOne<Konva.Layer>(`#${BACKGROUND_LAYER_ID}`)?.zIndex(++zIndex);
for (const layer of layers) {
stage.findOne<Konva.Layer>(`#${layer.id}`)?.zIndex(++zIndex);
}
for (const ca of controlAdapters) {
stage.findOne<Konva.Layer>(`#${ca.id}`)?.zIndex(++zIndex);
}
for (const rg of regions) {
stage.findOne<Konva.Layer>(`#${rg.id}`)?.zIndex(++zIndex);
}
stage.findOne<Konva.Layer>(`#${PREVIEW_LAYER_ID}`)?.zIndex(++zIndex);
};

View File

@ -64,7 +64,6 @@ export const renderRasterLayer = async (
stage: Konva.Stage, stage: Konva.Stage,
layerState: LayerData, layerState: LayerData,
tool: Tool, tool: Tool,
zIndex: number,
onPosChanged?: (arg: PosChangedArg, entityType: CanvasEntity['type']) => void onPosChanged?: (arg: PosChangedArg, entityType: CanvasEntity['type']) => void
) => { ) => {
const konvaLayer = const konvaLayer =
@ -75,7 +74,6 @@ export const renderRasterLayer = async (
listening: tool === 'move', // The layer only listens when using the move tool - otherwise the stage is handling mouse events listening: tool === 'move', // The layer only listens when using the move tool - otherwise the stage is handling mouse events
x: Math.floor(layerState.x), x: Math.floor(layerState.x),
y: Math.floor(layerState.y), y: Math.floor(layerState.y),
zIndex,
}); });
const konvaObjectGroup = const konvaObjectGroup =
@ -145,3 +143,20 @@ export const renderRasterLayer = async (
konvaObjectGroup.opacity(layerState.opacity); konvaObjectGroup.opacity(layerState.opacity);
}; };
export const renderLayers = (
stage: Konva.Stage,
layers: LayerData[],
tool: Tool,
onPosChanged?: (arg: PosChangedArg, entityType: CanvasEntity['type']) => void
): void => {
// Destroy nonexistent layers
for (const konvaLayer of stage.find<Konva.Layer>(`.${RASTER_LAYER_NAME}`)) {
if (!layers.find((l) => l.id === konvaLayer.id())) {
konvaLayer.destroy();
}
}
for (const layer of layers) {
renderRasterLayer(stage, layer, tool, onPosChanged);
}
};

View File

@ -83,7 +83,6 @@ export const renderRGLayer = (
rg: RegionalGuidanceData, rg: RegionalGuidanceData,
globalMaskLayerOpacity: number, globalMaskLayerOpacity: number,
tool: Tool, tool: Tool,
zIndex: number,
selectedEntity: CanvasEntity | null, selectedEntity: CanvasEntity | null,
onPosChanged?: (arg: PosChangedArg, entityType: CanvasEntity['type']) => void onPosChanged?: (arg: PosChangedArg, entityType: CanvasEntity['type']) => void
): void => { ): void => {
@ -94,7 +93,6 @@ export const renderRGLayer = (
listening: tool === 'move', // The layer only listens when using the move tool - otherwise the stage is handling mouse events listening: tool === 'move', // The layer only listens when using the move tool - otherwise the stage is handling mouse events
x: Math.floor(rg.x), x: Math.floor(rg.x),
y: Math.floor(rg.y), y: Math.floor(rg.y),
zIndex,
}); });
// Convert the color to a string, stripping the alpha - the object group will handle opacity. // Convert the color to a string, stripping the alpha - the object group will handle opacity.
@ -233,3 +231,22 @@ export const renderRGLayer = (
bboxRect.visible(false); bboxRect.visible(false);
} }
}; };
export const renderRegions = (
stage: Konva.Stage,
regions: RegionalGuidanceData[],
maskOpacity: number,
tool: Tool,
selectedEntity: CanvasEntity | null,
onPosChanged?: (arg: PosChangedArg, entityType: CanvasEntity['type']) => void
): void => {
// Destroy nonexistent layers
for (const konvaLayer of stage.find<Konva.Layer>(`.${RG_LAYER_NAME}`)) {
if (!regions.find((rg) => rg.id === konvaLayer.id())) {
konvaLayer.destroy();
}
}
for (const rg of regions) {
renderRGLayer(stage, rg, maskOpacity, tool, selectedEntity, onPosChanged);
}
};

View File

@ -2,15 +2,14 @@ import { Flex } from '@invoke-ai/ui-library';
import { useStore } from '@nanostores/react'; import { useStore } from '@nanostores/react';
import { StageComponent } from 'features/controlLayers/components/StageComponent'; import { StageComponent } from 'features/controlLayers/components/StageComponent';
import { $isPreviewVisible } from 'features/controlLayers/store/canvasV2Slice'; import { $isPreviewVisible } from 'features/controlLayers/store/canvasV2Slice';
import { AspectRatioIconPreview } from 'features/parameters/components/ImageSize/AspectRatioIconPreview';
import { memo } from 'react'; import { memo } from 'react';
export const AspectRatioCanvasPreview = memo(() => { export const AspectRatioCanvasPreview = memo(() => {
const isPreviewVisible = useStore($isPreviewVisible); const isPreviewVisible = useStore($isPreviewVisible);
if (!isPreviewVisible) { // if (!isPreviewVisible) {
return <AspectRatioIconPreview />; // return <AspectRatioIconPreview />;
} // }
return ( return (
<Flex w="full" h="full" alignItems="center" justifyContent="center" position="relative"> <Flex w="full" h="full" alignItems="center" justifyContent="center" position="relative">