tidy(ui): organise files

This commit is contained in:
psychedelicious 2024-07-04 21:07:05 +10:00
parent 79287c2d16
commit 73a7a27ea1
28 changed files with 505 additions and 473 deletions

View File

@ -1,6 +1,6 @@
import { enqueueRequested } from 'app/store/actions';
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
import { getNodeManager } from 'features/controlLayers/konva/KonvaNodeManager';
import { getCanvasManager } from 'features/controlLayers/konva/CanvasManager';
import { stagingAreaCanceledStaging, stagingAreaStartedStaging } from 'features/controlLayers/store/canvasV2Slice';
import { prepareLinearUIBatch } from 'features/nodes/util/graph/buildLinearBatchConfig';
import { buildSD1Graph } from 'features/nodes/util/graph/generation/buildSD1Graph';
@ -26,7 +26,7 @@ export const addEnqueueRequestedLinear = (startAppListening: AppStartListening)
try {
let g;
const manager = getNodeManager();
const manager = getCanvasManager();
assert(model, 'No model found in state');
const base = model.base;

View File

@ -3,7 +3,7 @@ import { logger } from 'app/logging/logger';
import { $isDebugging } from 'app/store/nanostores/isDebugging';
import { useAppStore } from 'app/store/storeHooks';
import { HeadsUpDisplay } from 'features/controlLayers/components/HeadsUpDisplay';
import { KonvaNodeManager, setNodeManager } from 'features/controlLayers/konva/KonvaNodeManager';
import { CanvasManager, setCanvasManager } from 'features/controlLayers/konva/CanvasManager';
import Konva from 'konva';
import { memo, useCallback, useEffect, useLayoutEffect, useState } from 'react';
import { useDevicePixelRatio } from 'use-device-pixel-ratio';
@ -35,8 +35,8 @@ const useStageRenderer = (stage: Konva.Stage, container: HTMLDivElement | null,
return () => {};
}
const manager = new KonvaNodeManager(stage, container, store, logIfDebugging);
setNodeManager(manager);
const manager = new CanvasManager(stage, container, store, logIfDebugging);
setCanvasManager(manager);
const cleanup = manager.initialize();
return cleanup;
}, [asPreview, container, stage, store]);

View File

@ -1,5 +1,5 @@
import { getArbitraryBaseColor } from '@invoke-ai/ui-library';
import type { KonvaNodeManager } from 'features/controlLayers/konva/KonvaNodeManager';
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
import Konva from 'konva';
const baseGridLineColor = getArbitraryBaseColor(27);
@ -31,9 +31,9 @@ const getGridSpacing = (scale: number): number => {
export class CanvasBackground {
layer: Konva.Layer;
manager: KonvaNodeManager;
manager: CanvasManager;
constructor(manager: KonvaNodeManager) {
constructor(manager: CanvasManager) {
this.manager = manager;
this.layer = new Konva.Layer({ listening: false });
}

View File

@ -1,5 +1,5 @@
import { roundToMultiple, roundToMultipleMin } from 'common/util/roundDownToMultiple';
import type { KonvaNodeManager } from 'features/controlLayers/konva/KonvaNodeManager';
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
import {
PREVIEW_GENERATION_BBOX_DUMMY_RECT,
PREVIEW_GENERATION_BBOX_GROUP,
@ -14,7 +14,7 @@ export class CanvasBbox {
group: Konva.Group;
rect: Konva.Rect;
transformer: Konva.Transformer;
manager: KonvaNodeManager;
manager: CanvasManager;
ALL_ANCHORS: string[] = [
'top-left',
@ -29,7 +29,7 @@ export class CanvasBbox {
CORNER_ANCHORS: string[] = ['top-left', 'top-right', 'bottom-left', 'bottom-right'];
NO_ANCHORS: string[] = [];
constructor(manager: KonvaNodeManager) {
constructor(manager: CanvasManager) {
this.manager = manager;
// Create a stash to hold onto the last aspect ratio of the bbox - this allows for locking the aspect ratio when
// transforming the bbox.

View File

@ -0,0 +1,53 @@
import { rgbaColorToString } from 'common/util/colorCodeTransformers';
import type { BrushLine } from 'features/controlLayers/store/types';
import Konva from 'konva';
export class CanvasBrushLine {
id: string;
konvaLineGroup: Konva.Group;
konvaLine: Konva.Line;
lastBrushLine: BrushLine;
constructor(brushLine: BrushLine) {
const { id, strokeWidth, clip, color, points } = brushLine;
this.id = id;
this.konvaLineGroup = new Konva.Group({
clip,
listening: false,
});
this.konvaLine = new Konva.Line({
id,
listening: false,
shadowForStrokeEnabled: false,
strokeWidth,
tension: 0,
lineCap: 'round',
lineJoin: 'round',
globalCompositeOperation: 'source-over',
stroke: rgbaColorToString(color),
points,
});
this.konvaLineGroup.add(this.konvaLine);
this.lastBrushLine = brushLine;
}
update(brushLine: BrushLine, force?: boolean): boolean {
if (this.lastBrushLine !== brushLine || force) {
const { points, color, clip, strokeWidth } = brushLine;
this.konvaLine.setAttrs({
points,
stroke: rgbaColorToString(color),
clip,
strokeWidth,
});
this.lastBrushLine = brushLine;
return true;
} else {
return false;
}
}
destroy() {
this.konvaLineGroup.destroy();
}
}

View File

@ -1,3 +1,4 @@
import { CanvasImage } from 'features/controlLayers/konva/CanvasImage';
import { LightnessToAlphaFilter } from 'features/controlLayers/konva/filters';
import { getObjectGroupId } from 'features/controlLayers/konva/naming';
import type { ControlAdapterEntity } from 'features/controlLayers/store/types';
@ -5,13 +6,11 @@ import Konva from 'konva';
import { isEqual } from 'lodash-es';
import { v4 as uuidv4 } from 'uuid';
import { KonvaImage } from './objects';
export class CanvasControlAdapter {
id: string;
layer: Konva.Layer;
group: Konva.Group;
image: KonvaImage | null;
image: CanvasImage | null;
constructor(entity: ControlAdapterEntity) {
const { id } = entity;
@ -43,7 +42,7 @@ export class CanvasControlAdapter {
const filters = entity.filter === 'LightnessToAlphaFilter' ? [LightnessToAlphaFilter] : [];
if (!this.image) {
this.image = await new KonvaImage(imageObject, {
this.image = await new CanvasImage(imageObject, {
onLoad: (konvaImage) => {
konvaImage.filters(filters);
konvaImage.cache();

View File

@ -1,6 +1,6 @@
import { getArbitraryBaseColor } from '@invoke-ai/ui-library';
import { DOCUMENT_FIT_PADDING_PX } from 'features/controlLayers/konva/constants';
import type { KonvaNodeManager } from 'features/controlLayers/konva/KonvaNodeManager';
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
import Konva from 'konva';
export class CanvasDocumentSizeOverlay {
@ -8,9 +8,9 @@ export class CanvasDocumentSizeOverlay {
outerRect: Konva.Rect;
innerRect: Konva.Rect;
padding: number;
manager: KonvaNodeManager;
manager: CanvasManager;
constructor(manager: KonvaNodeManager, padding?: number) {
constructor(manager: CanvasManager, padding?: number) {
this.manager = manager;
this.padding = padding ?? DOCUMENT_FIT_PADDING_PX;
this.group = new Konva.Group({ id: 'document_overlay_group', listening: false });

View File

@ -0,0 +1,70 @@
import { rgbaColorToString } from 'common/util/colorCodeTransformers';
import { getLayerBboxId, LAYER_BBOX_NAME } from 'features/controlLayers/konva/naming';
import type { CanvasEntity, EraserLine } from 'features/controlLayers/store/types';
import { RGBA_RED } from 'features/controlLayers/store/types';
import Konva from 'konva';
/**
* Creates a bounding box rect for a layer.
* @param entity The layer state for the layer to create the bounding box for
* @param konvaLayer The konva layer to attach the bounding box to
*/
export const createBboxRect = (entity: CanvasEntity, konvaLayer: Konva.Layer): Konva.Rect => {
const rect = new Konva.Rect({
id: getLayerBboxId(entity.id),
name: LAYER_BBOX_NAME,
strokeWidth: 1,
visible: false,
});
konvaLayer.add(rect);
return rect;
};
export class CanvasEraserLine {
id: string;
konvaLineGroup: Konva.Group;
konvaLine: Konva.Line;
lastEraserLine: EraserLine;
constructor(eraserLine: EraserLine) {
const { id, strokeWidth, clip, points } = eraserLine;
this.id = id;
this.konvaLineGroup = new Konva.Group({
clip,
listening: false,
});
this.konvaLine = new Konva.Line({
id,
listening: false,
shadowForStrokeEnabled: false,
strokeWidth,
tension: 0,
lineCap: 'round',
lineJoin: 'round',
globalCompositeOperation: 'destination-out',
stroke: rgbaColorToString(RGBA_RED),
points,
});
this.konvaLineGroup.add(this.konvaLine);
this.lastEraserLine = eraserLine;
}
update(eraserLine: EraserLine, force?: boolean): boolean {
if (this.lastEraserLine !== eraserLine || force) {
const { points, clip, strokeWidth } = eraserLine;
this.konvaLine.setAttrs({
points,
clip,
strokeWidth,
});
this.lastEraserLine = eraserLine;
return true;
} else {
return false;
}
}
destroy() {
this.konvaLineGroup.destroy();
}
}

View File

@ -0,0 +1,158 @@
import type { ImageObject } from 'features/controlLayers/store/types';
import { t } from 'i18next';
import Konva from 'konva';
import { getImageDTO as defaultGetImageDTO } from 'services/api/endpoints/images';
import type { ImageDTO } from 'services/api/types';
export class CanvasImage {
id: string;
konvaImageGroup: Konva.Group;
konvaPlaceholderGroup: Konva.Group;
konvaPlaceholderRect: Konva.Rect;
konvaPlaceholderText: Konva.Text;
imageName: string | null;
konvaImage: Konva.Image | null; // The image is loaded asynchronously, so it may not be available immediately
isLoading: boolean;
isError: boolean;
getImageDTO: (imageName: string) => Promise<ImageDTO | null>;
onLoading: () => void;
onLoad: (imageName: string, imageEl: HTMLImageElement) => void;
onError: () => void;
lastImageObject: ImageObject;
constructor(
imageObject: ImageObject,
options: {
getImageDTO?: (imageName: string) => Promise<ImageDTO | null>;
onLoading?: () => void;
onLoad?: (konvaImage: Konva.Image) => void;
onError?: () => void;
}
) {
const { getImageDTO, onLoading, onLoad, onError } = options;
const { id, width, height, x, y } = imageObject;
this.konvaImageGroup = new Konva.Group({ id, listening: false, x, y });
this.konvaPlaceholderGroup = new Konva.Group({ listening: false });
this.konvaPlaceholderRect = new Konva.Rect({
fill: 'hsl(220 12% 45% / 1)', // 'base.500'
width,
height,
listening: false,
});
this.konvaPlaceholderText = new Konva.Text({
fill: 'hsl(220 12% 10% / 1)', // 'base.900'
width,
height,
align: 'center',
verticalAlign: 'middle',
fontFamily: '"Inter Variable", sans-serif',
fontSize: width / 16,
fontStyle: '600',
text: t('common.loadingImage', 'Loading Image'),
listening: false,
});
this.konvaPlaceholderGroup.add(this.konvaPlaceholderRect);
this.konvaPlaceholderGroup.add(this.konvaPlaceholderText);
this.konvaImageGroup.add(this.konvaPlaceholderGroup);
this.id = id;
this.imageName = null;
this.konvaImage = null;
this.isLoading = false;
this.isError = false;
this.getImageDTO = getImageDTO ?? defaultGetImageDTO;
this.onLoading = function () {
this.isLoading = true;
if (!this.konvaImage) {
this.konvaPlaceholderGroup.visible(true);
this.konvaPlaceholderText.text(t('common.loadingImage', 'Loading Image'));
}
this.konvaImageGroup.visible(true);
if (onLoading) {
onLoading();
}
};
this.onLoad = function (imageName: string, imageEl: HTMLImageElement) {
if (this.konvaImage) {
this.konvaImage.setAttrs({
image: imageEl,
});
} else {
this.konvaImage = new Konva.Image({
id: this.id,
listening: false,
image: imageEl,
width,
height,
});
this.konvaImageGroup.add(this.konvaImage);
}
this.imageName = imageName;
this.isLoading = false;
this.isError = false;
this.konvaPlaceholderGroup.visible(false);
this.konvaImageGroup.visible(true);
if (onLoad) {
onLoad(this.konvaImage);
}
};
this.onError = function () {
this.imageName = null;
this.isLoading = false;
this.isError = true;
this.konvaPlaceholderGroup.visible(true);
this.konvaPlaceholderText.text(t('common.imageFailedToLoad', 'Image Failed to Load'));
this.konvaImageGroup.visible(true);
if (onError) {
onError();
}
};
this.lastImageObject = imageObject;
}
async updateImageSource(imageName: string) {
try {
this.onLoading();
const imageDTO = await this.getImageDTO(imageName);
if (!imageDTO) {
this.onError();
return;
}
const imageEl = new Image();
imageEl.onload = () => {
this.onLoad(imageName, imageEl);
};
imageEl.onerror = () => {
this.onError();
};
imageEl.id = imageName;
imageEl.src = imageDTO.image_url;
} catch {
this.onError();
}
}
async update(imageObject: ImageObject, force?: boolean): Promise<boolean> {
if (this.lastImageObject !== imageObject || force) {
const { width, height, x, y, image } = imageObject;
if (this.lastImageObject.image.name !== image.name || force) {
await this.updateImageSource(image.name);
}
this.konvaImage?.setAttrs({ x, y, width, height });
this.konvaPlaceholderRect.setAttrs({ width, height });
this.konvaPlaceholderText.setAttrs({ width, height, fontSize: width / 16 });
this.lastImageObject = imageObject;
return true;
} else {
return false;
}
}
destroy() {
this.konvaImageGroup.destroy();
}
}

View File

@ -1,8 +1,10 @@
import { rgbColorToString } from 'common/util/colorCodeTransformers';
import { CanvasBrushLine } from 'features/controlLayers/konva/CanvasBrushLine';
import { CanvasEraserLine } from 'features/controlLayers/konva/CanvasEraserLine';
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
import { CanvasRect } from 'features/controlLayers/konva/CanvasRect';
import { getNodeBboxFast } from 'features/controlLayers/konva/entityBbox';
import type { KonvaNodeManager } from 'features/controlLayers/konva/KonvaNodeManager';
import { getObjectGroupId, INPAINT_MASK_LAYER_ID } from 'features/controlLayers/konva/naming';
import { KonvaBrushLine, KonvaEraserLine, KonvaRect } from 'features/controlLayers/konva/objects';
import { mapId } from 'features/controlLayers/konva/util';
import { type InpaintMaskEntity, isDrawingTool } from 'features/controlLayers/store/types';
import Konva from 'konva';
@ -11,15 +13,15 @@ import { v4 as uuidv4 } from 'uuid';
export class CanvasInpaintMask {
id: string;
manager: KonvaNodeManager;
manager: CanvasManager;
layer: Konva.Layer;
group: Konva.Group;
objectsGroup: Konva.Group;
compositingRect: Konva.Rect;
transformer: Konva.Transformer;
objects: Map<string, KonvaBrushLine | KonvaEraserLine | KonvaRect>;
objects: Map<string, CanvasBrushLine | CanvasEraserLine | CanvasRect>;
constructor(manager: KonvaNodeManager) {
constructor(manager: CanvasManager) {
this.id = INPAINT_MASK_LAYER_ID;
this.manager = manager;
this.layer = new Konva.Layer({ id: INPAINT_MASK_LAYER_ID });
@ -84,10 +86,10 @@ export class CanvasInpaintMask {
for (const obj of inpaintMaskState.objects) {
if (obj.type === 'brush_line') {
let brushLine = this.objects.get(obj.id);
assert(brushLine instanceof KonvaBrushLine || brushLine === undefined);
assert(brushLine instanceof CanvasBrushLine || brushLine === undefined);
if (!brushLine) {
brushLine = new KonvaBrushLine(obj);
brushLine = new CanvasBrushLine(obj);
this.objects.set(brushLine.id, brushLine);
this.objectsGroup.add(brushLine.konvaLineGroup);
didDraw = true;
@ -98,10 +100,10 @@ export class CanvasInpaintMask {
}
} else if (obj.type === 'eraser_line') {
let eraserLine = this.objects.get(obj.id);
assert(eraserLine instanceof KonvaEraserLine || eraserLine === undefined);
assert(eraserLine instanceof CanvasEraserLine || eraserLine === undefined);
if (!eraserLine) {
eraserLine = new KonvaEraserLine(obj);
eraserLine = new CanvasEraserLine(obj);
this.objects.set(eraserLine.id, eraserLine);
this.objectsGroup.add(eraserLine.konvaLineGroup);
didDraw = true;
@ -112,10 +114,10 @@ export class CanvasInpaintMask {
}
} else if (obj.type === 'rect_shape') {
let rect = this.objects.get(obj.id);
assert(rect instanceof KonvaRect || rect === undefined);
assert(rect instanceof CanvasRect || rect === undefined);
if (!rect) {
rect = new KonvaRect(obj);
rect = new CanvasRect(obj);
this.objects.set(rect.id, rect);
this.objectsGroup.add(rect.konvaRect);
didDraw = true;

View File

@ -1,6 +1,9 @@
import type { KonvaNodeManager } from 'features/controlLayers/konva/KonvaNodeManager';
import { CanvasBrushLine } from 'features/controlLayers/konva/CanvasBrushLine';
import { CanvasEraserLine } from 'features/controlLayers/konva/CanvasEraserLine';
import { CanvasImage } from 'features/controlLayers/konva/CanvasImage';
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
import { CanvasRect } from 'features/controlLayers/konva/CanvasRect';
import { getObjectGroupId } from 'features/controlLayers/konva/naming';
import { KonvaBrushLine, KonvaEraserLine, KonvaImage, KonvaRect } from 'features/controlLayers/konva/objects';
import { mapId } from 'features/controlLayers/konva/util';
import { isDrawingTool, type LayerEntity } from 'features/controlLayers/store/types';
import Konva from 'konva';
@ -9,13 +12,13 @@ import { v4 as uuidv4 } from 'uuid';
export class CanvasLayer {
id: string;
manager: KonvaNodeManager;
manager: CanvasManager;
layer: Konva.Layer;
group: Konva.Group;
transformer: Konva.Transformer;
objects: Map<string, KonvaBrushLine | KonvaEraserLine | KonvaRect | KonvaImage>;
objects: Map<string, CanvasBrushLine | CanvasEraserLine | CanvasRect | CanvasImage>;
constructor(entity: LayerEntity, manager: KonvaNodeManager) {
constructor(entity: LayerEntity, manager: CanvasManager) {
this.id = entity.id;
this.manager = manager;
this.layer = new Konva.Layer({
@ -79,10 +82,10 @@ export class CanvasLayer {
for (const obj of layerState.objects) {
if (obj.type === 'brush_line') {
let brushLine = this.objects.get(obj.id);
assert(brushLine instanceof KonvaBrushLine || brushLine === undefined);
assert(brushLine instanceof CanvasBrushLine || brushLine === undefined);
if (!brushLine) {
brushLine = new KonvaBrushLine(obj);
brushLine = new CanvasBrushLine(obj);
this.objects.set(brushLine.id, brushLine);
this.group.add(brushLine.konvaLineGroup);
didDraw = true;
@ -93,10 +96,10 @@ export class CanvasLayer {
}
} else if (obj.type === 'eraser_line') {
let eraserLine = this.objects.get(obj.id);
assert(eraserLine instanceof KonvaEraserLine || eraserLine === undefined);
assert(eraserLine instanceof CanvasEraserLine || eraserLine === undefined);
if (!eraserLine) {
eraserLine = new KonvaEraserLine(obj);
eraserLine = new CanvasEraserLine(obj);
this.objects.set(eraserLine.id, eraserLine);
this.group.add(eraserLine.konvaLineGroup);
didDraw = true;
@ -107,10 +110,10 @@ export class CanvasLayer {
}
} else if (obj.type === 'rect_shape') {
let rect = this.objects.get(obj.id);
assert(rect instanceof KonvaRect || rect === undefined);
assert(rect instanceof CanvasRect || rect === undefined);
if (!rect) {
rect = new KonvaRect(obj);
rect = new CanvasRect(obj);
this.objects.set(rect.id, rect);
this.group.add(rect.konvaRect);
didDraw = true;
@ -121,10 +124,10 @@ export class CanvasLayer {
}
} else if (obj.type === 'image') {
let image = this.objects.get(obj.id);
assert(image instanceof KonvaImage || image === undefined);
assert(image instanceof CanvasImage || image === undefined);
if (!image) {
image = await new KonvaImage(obj, {
image = await new CanvasImage(obj, {
onLoad: () => {
this.updateGroup(true);
},

View File

@ -34,17 +34,17 @@ type Util = {
) => Promise<ImageDTO>;
};
const $nodeManager = atom<KonvaNodeManager | null>(null);
export function getNodeManager() {
const nodeManager = $nodeManager.get();
const $canvasManager = atom<CanvasManager | null>(null);
export function getCanvasManager() {
const nodeManager = $canvasManager.get();
assert(nodeManager !== null, 'Node manager not initialized');
return nodeManager;
}
export function setNodeManager(nodeManager: KonvaNodeManager) {
$nodeManager.set(nodeManager);
export function setCanvasManager(nodeManager: CanvasManager) {
$canvasManager.set(nodeManager);
}
export class KonvaNodeManager {
export class CanvasManager {
stage: Konva.Stage;
container: HTMLDivElement;
controlAdapters: Map<string, CanvasControlAdapter>;

View File

@ -0,0 +1,60 @@
import Konva from 'konva';
export class CanvasProgressImage {
id: string;
progressImageId: string | null;
konvaImageGroup: Konva.Group;
konvaImage: Konva.Image | null; // The image is loaded asynchronously, so it may not be available immediately
isLoading: boolean;
isError: boolean;
constructor(arg: { id: string }) {
const { id } = arg;
this.konvaImageGroup = new Konva.Group({ id, listening: false });
this.id = id;
this.progressImageId = null;
this.konvaImage = null;
this.isLoading = false;
this.isError = false;
}
async updateImageSource(
progressImageId: string,
dataURL: string,
x: number,
y: number,
width: number,
height: number
) {
const imageEl = new Image();
imageEl.onload = () => {
if (this.konvaImage) {
this.konvaImage.setAttrs({
image: imageEl,
x,
y,
width,
height,
});
} else {
this.konvaImage = new Konva.Image({
id: this.id,
listening: false,
image: imageEl,
x,
y,
width,
height,
});
this.konvaImageGroup.add(this.konvaImage);
}
};
imageEl.id = progressImageId;
imageEl.src = dataURL;
}
destroy() {
this.konvaImageGroup.destroy();
}
}

View File

@ -0,0 +1,46 @@
import { rgbaColorToString } from 'common/util/colorCodeTransformers';
import type { RectShape } from 'features/controlLayers/store/types';
import Konva from 'konva';
export class CanvasRect {
id: string;
konvaRect: Konva.Rect;
lastRectShape: RectShape;
constructor(rectShape: RectShape) {
const { id, x, y, width, height } = rectShape;
this.id = id;
const konvaRect = new Konva.Rect({
id,
x,
y,
width,
height,
listening: false,
fill: rgbaColorToString(rectShape.color),
});
this.konvaRect = konvaRect;
this.lastRectShape = rectShape;
}
update(rectShape: RectShape, force?: boolean): boolean {
if (this.lastRectShape !== rectShape || force) {
const { x, y, width, height, color } = rectShape;
this.konvaRect.setAttrs({
x,
y,
width,
height,
fill: rgbaColorToString(color),
});
this.lastRectShape = rectShape;
return true;
} else {
return false;
}
}
destroy() {
this.konvaRect.destroy();
}
}

View File

@ -1,8 +1,10 @@
import { rgbColorToString } from 'common/util/colorCodeTransformers';
import { CanvasBrushLine } from 'features/controlLayers/konva/CanvasBrushLine';
import { CanvasEraserLine } from 'features/controlLayers/konva/CanvasEraserLine';
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
import { CanvasRect } from 'features/controlLayers/konva/CanvasRect';
import { getNodeBboxFast } from 'features/controlLayers/konva/entityBbox';
import type { KonvaNodeManager } from 'features/controlLayers/konva/KonvaNodeManager';
import { getObjectGroupId } from 'features/controlLayers/konva/naming';
import { KonvaBrushLine, KonvaEraserLine, KonvaRect } from 'features/controlLayers/konva/objects';
import { mapId } from 'features/controlLayers/konva/util';
import { isDrawingTool, type RegionEntity } from 'features/controlLayers/store/types';
import Konva from 'konva';
@ -11,15 +13,15 @@ import { v4 as uuidv4 } from 'uuid';
export class CanvasRegion {
id: string;
manager: KonvaNodeManager;
manager: CanvasManager;
layer: Konva.Layer;
group: Konva.Group;
objectsGroup: Konva.Group;
compositingRect: Konva.Rect;
transformer: Konva.Transformer;
objects: Map<string, KonvaBrushLine | KonvaEraserLine | KonvaRect>;
objects: Map<string, CanvasBrushLine | CanvasEraserLine | CanvasRect>;
constructor(entity: RegionEntity, manager: KonvaNodeManager) {
constructor(entity: RegionEntity, manager: CanvasManager) {
this.id = entity.id;
this.manager = manager;
this.layer = new Konva.Layer({ id: entity.id });
@ -84,10 +86,10 @@ export class CanvasRegion {
for (const obj of regionState.objects) {
if (obj.type === 'brush_line') {
let brushLine = this.objects.get(obj.id);
assert(brushLine instanceof KonvaBrushLine || brushLine === undefined);
assert(brushLine instanceof CanvasBrushLine || brushLine === undefined);
if (!brushLine) {
brushLine = new KonvaBrushLine(obj);
brushLine = new CanvasBrushLine(obj);
this.objects.set(brushLine.id, brushLine);
this.objectsGroup.add(brushLine.konvaLineGroup);
didDraw = true;
@ -98,10 +100,10 @@ export class CanvasRegion {
}
} else if (obj.type === 'eraser_line') {
let eraserLine = this.objects.get(obj.id);
assert(eraserLine instanceof KonvaEraserLine || eraserLine === undefined);
assert(eraserLine instanceof CanvasEraserLine || eraserLine === undefined);
if (!eraserLine) {
eraserLine = new KonvaEraserLine(obj);
eraserLine = new CanvasEraserLine(obj);
this.objects.set(eraserLine.id, eraserLine);
this.objectsGroup.add(eraserLine.konvaLineGroup);
didDraw = true;
@ -112,10 +114,10 @@ export class CanvasRegion {
}
} else if (obj.type === 'rect_shape') {
let rect = this.objects.get(obj.id);
assert(rect instanceof KonvaRect || rect === undefined);
assert(rect instanceof CanvasRect || rect === undefined);
if (!rect) {
rect = new KonvaRect(obj);
rect = new CanvasRect(obj);
this.objects.set(rect.id, rect);
this.objectsGroup.add(rect.konvaRect);
didDraw = true;

View File

@ -1,16 +1,17 @@
import type { KonvaNodeManager } from 'features/controlLayers/konva/KonvaNodeManager';
import { KonvaImage, KonvaProgressImage } from 'features/controlLayers/konva/objects';
import { CanvasImage } from 'features/controlLayers/konva/CanvasImage';
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
import { CanvasProgressImage } from 'features/controlLayers/konva/CanvasProgressImage';
import Konva from 'konva';
import type { ImageDTO } from 'services/api/types';
export class CanvasStagingArea {
group: Konva.Group;
image: KonvaImage | null;
progressImage: KonvaProgressImage | null;
image: CanvasImage | null;
progressImage: CanvasProgressImage | null;
imageDTO: ImageDTO | null;
manager: KonvaNodeManager;
manager: CanvasManager;
constructor(manager: KonvaNodeManager) {
constructor(manager: CanvasManager) {
this.manager = manager;
this.group = new Konva.Group({ listening: false });
this.image = null;
@ -37,7 +38,7 @@ export class CanvasStagingArea {
this.progressImage?.konvaImageGroup.visible(false);
} else {
const { image_name, width, height } = this.imageDTO;
this.image = new KonvaImage(
this.image = new CanvasImage(
{
id: 'staging-area-image',
type: 'image',
@ -85,7 +86,7 @@ export class CanvasStagingArea {
this.progressImage.konvaImageGroup.visible(true);
}
} else {
this.progressImage = new KonvaProgressImage({ id: 'progress-image' });
this.progressImage = new CanvasProgressImage({ id: 'progress-image' });
this.group.add(this.progressImage.konvaImageGroup);
await this.progressImage.updateImageSource(progressImageId, dataURL, x, y, width, height);
this.image?.konvaImageGroup.visible(false);

View File

@ -1,15 +1,15 @@
import { rgbaColorToString } from 'common/util/colorCodeTransformers';
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
import {
BRUSH_BORDER_INNER_COLOR,
BRUSH_BORDER_OUTER_COLOR,
BRUSH_ERASER_BORDER_WIDTH,
} from 'features/controlLayers/konva/constants';
import type { KonvaNodeManager } from 'features/controlLayers/konva/KonvaNodeManager';
import { PREVIEW_RECT_ID } from 'features/controlLayers/konva/naming';
import Konva from 'konva';
export class CanvasTool {
manager: KonvaNodeManager;
manager: CanvasManager;
group: Konva.Group;
brush: {
group: Konva.Group;
@ -28,7 +28,7 @@ export class CanvasTool {
fillRect: Konva.Rect;
};
constructor(manager: KonvaNodeManager) {
constructor(manager: CanvasManager) {
this.manager = manager;
this.group = new Konva.Group();

View File

@ -1,11 +1,11 @@
import openBase64ImageInTab from 'common/util/openBase64ImageInTab';
import {
CA_LAYER_IMAGE_NAME,
getLayerBboxId,
LAYER_BBOX_NAME,
RASTER_LAYER_OBJECT_GROUP_NAME,
RG_LAYER_OBJECT_GROUP_NAME,
} from 'features/controlLayers/konva/naming';
import { createBboxRect } from 'features/controlLayers/konva/objects';
import { imageDataToDataURL } from 'features/controlLayers/konva/util';
import type {
BboxChangedArg,
@ -18,6 +18,22 @@ import Konva from 'konva';
import type { IRect } from 'konva/lib/types';
import { assert } from 'tsafe';
/**
* Creates a bounding box rect for a layer.
* @param entity The layer state for the layer to create the bounding box for
* @param konvaLayer The konva layer to attach the bounding box to
*/
export const createBboxRect = (entity: CanvasEntity, konvaLayer: Konva.Layer): Konva.Rect => {
const rect = new Konva.Rect({
id: getLayerBboxId(entity.id),
name: LAYER_BBOX_NAME,
strokeWidth: 1,
visible: false,
});
konvaLayer.add(rect);
return rect;
};
/**
* Logic to create and render bounding boxes for layers.
* Some utils are included for calculating bounding boxes.

View File

@ -1,4 +1,4 @@
import type { KonvaNodeManager } from 'features/controlLayers/konva/KonvaNodeManager';
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
import { getScaledCursorPosition } from 'features/controlLayers/konva/util';
import type { CanvasEntity } from 'features/controlLayers/store/types';
import type Konva from 'konva';
@ -23,7 +23,7 @@ import { PREVIEW_TOOL_GROUP_ID } from './naming';
*/
const updateLastCursorPos = (
stage: Konva.Stage,
setLastCursorPos: KonvaNodeManager['stateApi']['setLastCursorPos']
setLastCursorPos: CanvasManager['stateApi']['setLastCursorPos']
) => {
const pos = getScaledCursorPosition(stage);
if (!pos) {
@ -56,10 +56,10 @@ const calculateNewBrushSize = (brushSize: number, delta: number) => {
const maybeAddNextPoint = (
selectedEntity: CanvasEntity,
currentPos: Vector2d,
getToolState: KonvaNodeManager['stateApi']['getToolState'],
getLastAddedPoint: KonvaNodeManager['stateApi']['getLastAddedPoint'],
setLastAddedPoint: KonvaNodeManager['stateApi']['setLastAddedPoint'],
onPointAddedToLine: KonvaNodeManager['stateApi']['onPointAddedToLine']
getToolState: CanvasManager['stateApi']['getToolState'],
getLastAddedPoint: CanvasManager['stateApi']['getLastAddedPoint'],
setLastAddedPoint: CanvasManager['stateApi']['setLastAddedPoint'],
onPointAddedToLine: CanvasManager['stateApi']['onPointAddedToLine']
) => {
const isDrawableEntity =
selectedEntity?.type === 'regional_guidance' ||
@ -95,7 +95,7 @@ const maybeAddNextPoint = (
);
};
export const setStageEventHandlers = (manager: KonvaNodeManager): (() => void) => {
export const setStageEventHandlers = (manager: CanvasManager): (() => void) => {
const { stage, stateApi } = manager;
const {
getToolState,

View File

@ -1,378 +0,0 @@
import { rgbaColorToString } from 'common/util/colorCodeTransformers';
import { getLayerBboxId, LAYER_BBOX_NAME } from 'features/controlLayers/konva/naming';
import type { BrushLine, CanvasEntity, EraserLine, ImageObject, RectShape } from 'features/controlLayers/store/types';
import { RGBA_RED } from 'features/controlLayers/store/types';
import { t } from 'i18next';
import Konva from 'konva';
import { getImageDTO as defaultGetImageDTO } from 'services/api/endpoints/images';
import type { ImageDTO } from 'services/api/types';
/**
* Creates a bounding box rect for a layer.
* @param entity The layer state for the layer to create the bounding box for
* @param konvaLayer The konva layer to attach the bounding box to
*/
export const createBboxRect = (entity: CanvasEntity, konvaLayer: Konva.Layer): Konva.Rect => {
const rect = new Konva.Rect({
id: getLayerBboxId(entity.id),
name: LAYER_BBOX_NAME,
strokeWidth: 1,
visible: false,
});
konvaLayer.add(rect);
return rect;
};
export class KonvaBrushLine {
id: string;
konvaLineGroup: Konva.Group;
konvaLine: Konva.Line;
lastBrushLine: BrushLine;
constructor(brushLine: BrushLine) {
const { id, strokeWidth, clip, color, points } = brushLine;
this.id = id;
this.konvaLineGroup = new Konva.Group({
clip,
listening: false,
});
this.konvaLine = new Konva.Line({
id,
listening: false,
shadowForStrokeEnabled: false,
strokeWidth,
tension: 0,
lineCap: 'round',
lineJoin: 'round',
globalCompositeOperation: 'source-over',
stroke: rgbaColorToString(color),
points,
});
this.konvaLineGroup.add(this.konvaLine);
this.lastBrushLine = brushLine;
}
update(brushLine: BrushLine, force?: boolean): boolean {
if (this.lastBrushLine !== brushLine || force) {
const { points, color, clip, strokeWidth } = brushLine;
this.konvaLine.setAttrs({
points,
stroke: rgbaColorToString(color),
clip,
strokeWidth,
});
this.lastBrushLine = brushLine;
return true;
} else {
return false;
}
}
destroy() {
this.konvaLineGroup.destroy();
}
}
export class KonvaEraserLine {
id: string;
konvaLineGroup: Konva.Group;
konvaLine: Konva.Line;
lastEraserLine: EraserLine;
constructor(eraserLine: EraserLine) {
const { id, strokeWidth, clip, points } = eraserLine;
this.id = id;
this.konvaLineGroup = new Konva.Group({
clip,
listening: false,
});
this.konvaLine = new Konva.Line({
id,
listening: false,
shadowForStrokeEnabled: false,
strokeWidth,
tension: 0,
lineCap: 'round',
lineJoin: 'round',
globalCompositeOperation: 'destination-out',
stroke: rgbaColorToString(RGBA_RED),
points,
});
this.konvaLineGroup.add(this.konvaLine);
this.lastEraserLine = eraserLine;
}
update(eraserLine: EraserLine, force?: boolean): boolean {
if (this.lastEraserLine !== eraserLine || force) {
const { points, clip, strokeWidth } = eraserLine;
this.konvaLine.setAttrs({
points,
clip,
strokeWidth,
});
this.lastEraserLine = eraserLine;
return true;
} else {
return false;
}
}
destroy() {
this.konvaLineGroup.destroy();
}
}
export class KonvaRect {
id: string;
konvaRect: Konva.Rect;
lastRectShape: RectShape;
constructor(rectShape: RectShape) {
const { id, x, y, width, height } = rectShape;
this.id = id;
const konvaRect = new Konva.Rect({
id,
x,
y,
width,
height,
listening: false,
fill: rgbaColorToString(rectShape.color),
});
this.konvaRect = konvaRect;
this.lastRectShape = rectShape;
}
update(rectShape: RectShape, force?: boolean): boolean {
if (this.lastRectShape !== rectShape || force) {
const { x, y, width, height, color } = rectShape;
this.konvaRect.setAttrs({
x,
y,
width,
height,
fill: rgbaColorToString(color),
});
this.lastRectShape = rectShape;
return true;
} else {
return false;
}
}
destroy() {
this.konvaRect.destroy();
}
}
export class KonvaImage {
id: string;
konvaImageGroup: Konva.Group;
konvaPlaceholderGroup: Konva.Group;
konvaPlaceholderRect: Konva.Rect;
konvaPlaceholderText: Konva.Text;
imageName: string | null;
konvaImage: Konva.Image | null; // The image is loaded asynchronously, so it may not be available immediately
isLoading: boolean;
isError: boolean;
getImageDTO: (imageName: string) => Promise<ImageDTO | null>;
onLoading: () => void;
onLoad: (imageName: string, imageEl: HTMLImageElement) => void;
onError: () => void;
lastImageObject: ImageObject;
constructor(
imageObject: ImageObject,
options: {
getImageDTO?: (imageName: string) => Promise<ImageDTO | null>;
onLoading?: () => void;
onLoad?: (konvaImage: Konva.Image) => void;
onError?: () => void;
}
) {
const { getImageDTO, onLoading, onLoad, onError } = options;
const { id, width, height, x, y } = imageObject;
this.konvaImageGroup = new Konva.Group({ id, listening: false, x, y });
this.konvaPlaceholderGroup = new Konva.Group({ listening: false });
this.konvaPlaceholderRect = new Konva.Rect({
fill: 'hsl(220 12% 45% / 1)', // 'base.500'
width,
height,
listening: false,
});
this.konvaPlaceholderText = new Konva.Text({
fill: 'hsl(220 12% 10% / 1)', // 'base.900'
width,
height,
align: 'center',
verticalAlign: 'middle',
fontFamily: '"Inter Variable", sans-serif',
fontSize: width / 16,
fontStyle: '600',
text: t('common.loadingImage', 'Loading Image'),
listening: false,
});
this.konvaPlaceholderGroup.add(this.konvaPlaceholderRect);
this.konvaPlaceholderGroup.add(this.konvaPlaceholderText);
this.konvaImageGroup.add(this.konvaPlaceholderGroup);
this.id = id;
this.imageName = null;
this.konvaImage = null;
this.isLoading = false;
this.isError = false;
this.getImageDTO = getImageDTO ?? defaultGetImageDTO;
this.onLoading = function () {
this.isLoading = true;
if (!this.konvaImage) {
this.konvaPlaceholderGroup.visible(true);
this.konvaPlaceholderText.text(t('common.loadingImage', 'Loading Image'));
}
this.konvaImageGroup.visible(true);
if (onLoading) {
onLoading();
}
};
this.onLoad = function (imageName: string, imageEl: HTMLImageElement) {
if (this.konvaImage) {
this.konvaImage.setAttrs({
image: imageEl,
});
} else {
this.konvaImage = new Konva.Image({
id: this.id,
listening: false,
image: imageEl,
width,
height,
});
this.konvaImageGroup.add(this.konvaImage);
}
this.imageName = imageName;
this.isLoading = false;
this.isError = false;
this.konvaPlaceholderGroup.visible(false);
this.konvaImageGroup.visible(true);
if (onLoad) {
onLoad(this.konvaImage);
}
};
this.onError = function () {
this.imageName = null;
this.isLoading = false;
this.isError = true;
this.konvaPlaceholderGroup.visible(true);
this.konvaPlaceholderText.text(t('common.imageFailedToLoad', 'Image Failed to Load'));
this.konvaImageGroup.visible(true);
if (onError) {
onError();
}
};
this.lastImageObject = imageObject;
}
async updateImageSource(imageName: string) {
try {
this.onLoading();
const imageDTO = await this.getImageDTO(imageName);
if (!imageDTO) {
this.onError();
return;
}
const imageEl = new Image();
imageEl.onload = () => {
this.onLoad(imageName, imageEl);
};
imageEl.onerror = () => {
this.onError();
};
imageEl.id = imageName;
imageEl.src = imageDTO.image_url;
} catch {
this.onError();
}
}
async update(imageObject: ImageObject, force?: boolean): Promise<boolean> {
if (this.lastImageObject !== imageObject || force) {
const { width, height, x, y, image } = imageObject;
if (this.lastImageObject.image.name !== image.name || force) {
await this.updateImageSource(image.name);
}
this.konvaImage?.setAttrs({ x, y, width, height });
this.konvaPlaceholderRect.setAttrs({ width, height });
this.konvaPlaceholderText.setAttrs({ width, height, fontSize: width / 16 });
this.lastImageObject = imageObject;
return true;
} else {
return false;
}
}
destroy() {
this.konvaImageGroup.destroy();
}
}
export class KonvaProgressImage {
id: string;
progressImageId: string | null;
konvaImageGroup: Konva.Group;
konvaImage: Konva.Image | null; // The image is loaded asynchronously, so it may not be available immediately
isLoading: boolean;
isError: boolean;
constructor(arg: { id: string }) {
const { id } = arg;
this.konvaImageGroup = new Konva.Group({ id, listening: false });
this.id = id;
this.progressImageId = null;
this.konvaImage = null;
this.isLoading = false;
this.isError = false;
}
async updateImageSource(
progressImageId: string,
dataURL: string,
x: number,
y: number,
width: number,
height: number
) {
const imageEl = new Image();
imageEl.onload = () => {
if (this.konvaImage) {
this.konvaImage.setAttrs({
image: imageEl,
x,
y,
width,
height,
});
} else {
this.konvaImage = new Konva.Image({
id: this.id,
listening: false,
image: imageEl,
x,
y,
width,
height,
});
this.konvaImageGroup.add(this.konvaImage);
}
};
imageEl.id = progressImageId;
imageEl.src = dataURL;
}
destroy() {
this.konvaImageGroup.destroy();
}
}

View File

@ -1,4 +1,4 @@
import type { KonvaNodeManager } from 'features/controlLayers/konva/KonvaNodeManager';
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
import type { CanvasV2State, Size } from 'features/controlLayers/store/types';
import type { Graph } from 'features/nodes/util/graph/generation/Graph';
import { isEqual, pick } from 'lodash-es';
@ -6,7 +6,7 @@ import type { Invocation } from 'services/api/types';
export const addImageToImage = async (
g: Graph,
manager: KonvaNodeManager,
manager: CanvasManager,
l2i: Invocation<'l2i'>,
denoise: Invocation<'denoise_latents'>,
vaeSource: Invocation<'main_model_loader' | 'sdxl_model_loader' | 'seamless' | 'vae_loader'>,

View File

@ -1,4 +1,4 @@
import type { KonvaNodeManager } from 'features/controlLayers/konva/KonvaNodeManager';
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
import type { CanvasV2State, Size } from 'features/controlLayers/store/types';
import type { Graph } from 'features/nodes/util/graph/generation/Graph';
import type { ParameterPrecision } from 'features/parameters/types/parameterSchemas';
@ -7,7 +7,7 @@ import type { Invocation } from 'services/api/types';
export const addInpaint = async (
g: Graph,
manager: KonvaNodeManager,
manager: CanvasManager,
l2i: Invocation<'l2i'>,
denoise: Invocation<'denoise_latents'>,
vaeSource: Invocation<'main_model_loader' | 'sdxl_model_loader' | 'seamless' | 'vae_loader'>,

View File

@ -1,4 +1,4 @@
import type { KonvaNodeManager } from 'features/controlLayers/konva/KonvaNodeManager';
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
import type { CanvasV2State, Size } from 'features/controlLayers/store/types';
import type { Graph } from 'features/nodes/util/graph/generation/Graph';
import { getInfill } from 'features/nodes/util/graph/graphBuilderUtils';
@ -8,7 +8,7 @@ import type { Invocation } from 'services/api/types';
export const addOutpaint = async (
g: Graph,
manager: KonvaNodeManager,
manager: CanvasManager,
l2i: Invocation<'l2i'>,
denoise: Invocation<'denoise_latents'>,
vaeSource: Invocation<'main_model_loader' | 'sdxl_model_loader' | 'seamless' | 'vae_loader'>,

View File

@ -1,5 +1,5 @@
import { deepClone } from 'common/util/deepClone';
import type { KonvaNodeManager } from 'features/controlLayers/konva/KonvaNodeManager';
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
import type { IPAdapterEntity, Rect, RegionEntity } from 'features/controlLayers/store/types';
import {
PROMPT_REGION_INVERT_TENSOR_MASK_PREFIX,
@ -27,7 +27,7 @@ import { assert } from 'tsafe';
*/
export const addRegions = async (
manager: KonvaNodeManager,
manager: CanvasManager,
regions: RegionEntity[],
g: Graph,
bbox: Rect,

View File

@ -1,5 +1,5 @@
import type { RootState } from 'app/store/store';
import type { KonvaNodeManager } from 'features/controlLayers/konva/KonvaNodeManager';
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
import { fetchModelConfigWithTypeGuard } from 'features/metadata/util/modelFetchingHelpers';
import {
LATENTS_TO_IMAGE,
@ -30,7 +30,7 @@ import { addRegions } from './addRegions';
export const buildImageToImageSDXLGraph = async (
state: RootState,
manager: KonvaNodeManager
manager: CanvasManager
): Promise<NonNullableGraph> => {
const { bbox, params } = state.canvasV2;
const {

View File

@ -1,5 +1,5 @@
import type { RootState } from 'app/store/store';
import type { KonvaNodeManager } from 'features/controlLayers/konva/KonvaNodeManager';
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
import { fetchModelConfigWithTypeGuard } from 'features/metadata/util/modelFetchingHelpers';
import {
CANVAS_OUTPUT,
@ -34,7 +34,7 @@ import { assert } from 'tsafe';
import { addRegions } from './addRegions';
export const buildSD1Graph = async (state: RootState, manager: KonvaNodeManager): Promise<Graph> => {
export const buildSD1Graph = async (state: RootState, manager: CanvasManager): Promise<Graph> => {
const generationMode = manager.getGenerationMode();
const { bbox, params } = state.canvasV2;

View File

@ -1,5 +1,5 @@
import type { RootState } from 'app/store/store';
import type { KonvaNodeManager } from 'features/controlLayers/konva/KonvaNodeManager';
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
import { fetchModelConfigWithTypeGuard } from 'features/metadata/util/modelFetchingHelpers';
import {
CANVAS_OUTPUT,
@ -33,7 +33,7 @@ import { assert } from 'tsafe';
import { addRegions } from './addRegions';
export const buildSDXLGraph = async (state: RootState, manager: KonvaNodeManager): Promise<Graph> => {
export const buildSDXLGraph = async (state: RootState, manager: CanvasManager): Promise<Graph> => {
const generationMode = manager.getGenerationMode();
const { bbox, params } = state.canvasV2;

View File

@ -1,5 +1,5 @@
import type { RootState } from 'app/store/store';
import type { KonvaNodeManager } from 'features/controlLayers/konva/KonvaNodeManager';
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
import { fetchModelConfigWithTypeGuard } from 'features/metadata/util/modelFetchingHelpers';
import {
CLIP_SKIP,
@ -31,7 +31,7 @@ import { assert } from 'tsafe';
import { addRegions } from './addRegions';
export const buildTextToImageSD1SD2Graph = async (state: RootState, manager: KonvaNodeManager): Promise<GraphType> => {
export const buildTextToImageSD1SD2Graph = async (state: RootState, manager: CanvasManager): Promise<GraphType> => {
const { bbox, params } = state.canvasV2;
const {