diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/enqueueRequestedLinear.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/enqueueRequestedLinear.ts index 0064614667..6359e55e40 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/enqueueRequestedLinear.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/enqueueRequestedLinear.ts @@ -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; diff --git a/invokeai/frontend/web/src/features/controlLayers/components/StageComponent.tsx b/invokeai/frontend/web/src/features/controlLayers/components/StageComponent.tsx index 6cafcd1066..8604d95142 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/StageComponent.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/StageComponent.tsx @@ -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]); diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasBackground.ts b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasBackground.ts index 01c5980cab..5bf87510d8 100644 --- a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasBackground.ts +++ b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasBackground.ts @@ -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 }); } diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasBbox.ts b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasBbox.ts index d6dd391484..7ab216c4bf 100644 --- a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasBbox.ts +++ b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasBbox.ts @@ -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. diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasBrushLine.ts b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasBrushLine.ts new file mode 100644 index 0000000000..9ce10450a1 --- /dev/null +++ b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasBrushLine.ts @@ -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(); + } +} diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasControlAdapter.ts b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasControlAdapter.ts index 421ec4ad9a..aae0a5fcb8 100644 --- a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasControlAdapter.ts +++ b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasControlAdapter.ts @@ -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(); diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasDocumentSizeOverlay.ts b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasDocumentSizeOverlay.ts index be780449db..8b278a5636 100644 --- a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasDocumentSizeOverlay.ts +++ b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasDocumentSizeOverlay.ts @@ -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 }); diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasEraserLine.ts b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasEraserLine.ts new file mode 100644 index 0000000000..f1ce21afd1 --- /dev/null +++ b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasEraserLine.ts @@ -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(); + } +} diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasImage.ts b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasImage.ts new file mode 100644 index 0000000000..575fcc4e64 --- /dev/null +++ b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasImage.ts @@ -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; + onLoading: () => void; + onLoad: (imageName: string, imageEl: HTMLImageElement) => void; + onError: () => void; + lastImageObject: ImageObject; + + constructor( + imageObject: ImageObject, + options: { + getImageDTO?: (imageName: string) => Promise; + 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 { + 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(); + } +} diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasInpaintMask.ts b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasInpaintMask.ts index 0151e896b2..59ef9e7759 100644 --- a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasInpaintMask.ts +++ b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasInpaintMask.ts @@ -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 { getObjectGroupId, INPAINT_MASK_LAYER_ID } from 'features/controlLayers/konva/naming'; 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; + objects: Map; - 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; diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasLayer.ts b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasLayer.ts index a5660bdfc4..3288e710a4 100644 --- a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasLayer.ts +++ b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasLayer.ts @@ -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; + objects: Map; - 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); }, diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/KonvaNodeManager.ts b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasManager.ts similarity index 98% rename from invokeai/frontend/web/src/features/controlLayers/konva/KonvaNodeManager.ts rename to invokeai/frontend/web/src/features/controlLayers/konva/CanvasManager.ts index 796efae4a7..6d5fbcf7c4 100644 --- a/invokeai/frontend/web/src/features/controlLayers/konva/KonvaNodeManager.ts +++ b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasManager.ts @@ -34,17 +34,17 @@ type Util = { ) => Promise; }; -const $nodeManager = atom(null); -export function getNodeManager() { - const nodeManager = $nodeManager.get(); +const $canvasManager = atom(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; diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasProgressImage.ts b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasProgressImage.ts new file mode 100644 index 0000000000..e56f88e164 --- /dev/null +++ b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasProgressImage.ts @@ -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(); + } +} diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasRect.ts b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasRect.ts new file mode 100644 index 0000000000..a5a8eea8b9 --- /dev/null +++ b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasRect.ts @@ -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(); + } +} diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasRegion.ts b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasRegion.ts index 9d72f02744..57bda0a1ef 100644 --- a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasRegion.ts +++ b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasRegion.ts @@ -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; + objects: Map; - 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; diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasStagingArea.ts b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasStagingArea.ts index 9f5ce759e6..76d793ecb2 100644 --- a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasStagingArea.ts +++ b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasStagingArea.ts @@ -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); diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasTool.ts b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasTool.ts index ed6e0d35ee..825127a596 100644 --- a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasTool.ts +++ b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasTool.ts @@ -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(); diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/entityBbox.ts b/invokeai/frontend/web/src/features/controlLayers/konva/entityBbox.ts index f0bb69bb32..1448d5a8f7 100644 --- a/invokeai/frontend/web/src/features/controlLayers/konva/entityBbox.ts +++ b/invokeai/frontend/web/src/features/controlLayers/konva/entityBbox.ts @@ -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. diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/events.ts b/invokeai/frontend/web/src/features/controlLayers/konva/events.ts index bf76ffbfe9..4967982604 100644 --- a/invokeai/frontend/web/src/features/controlLayers/konva/events.ts +++ b/invokeai/frontend/web/src/features/controlLayers/konva/events.ts @@ -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, diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/objects.ts b/invokeai/frontend/web/src/features/controlLayers/konva/objects.ts deleted file mode 100644 index 3a6ecbd060..0000000000 --- a/invokeai/frontend/web/src/features/controlLayers/konva/objects.ts +++ /dev/null @@ -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; - onLoading: () => void; - onLoad: (imageName: string, imageEl: HTMLImageElement) => void; - onError: () => void; - lastImageObject: ImageObject; - - constructor( - imageObject: ImageObject, - options: { - getImageDTO?: (imageName: string) => Promise; - 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 { - 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(); - } -} diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/generation/addImageToImage.ts b/invokeai/frontend/web/src/features/nodes/util/graph/generation/addImageToImage.ts index c3b2953113..953e3505ef 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/generation/addImageToImage.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/generation/addImageToImage.ts @@ -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'>, diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/generation/addInpaint.ts b/invokeai/frontend/web/src/features/nodes/util/graph/generation/addInpaint.ts index f578ff8202..0b4520385f 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/generation/addInpaint.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/generation/addInpaint.ts @@ -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'>, diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/generation/addOutpaint.ts b/invokeai/frontend/web/src/features/nodes/util/graph/generation/addOutpaint.ts index c4b828e275..e5c774d953 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/generation/addOutpaint.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/generation/addOutpaint.ts @@ -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'>, diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/generation/addRegions.ts b/invokeai/frontend/web/src/features/nodes/util/graph/generation/addRegions.ts index 86c1d66491..55b283a7f1 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/generation/addRegions.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/generation/addRegions.ts @@ -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, diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/generation/buildImageToImageSDXLGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graph/generation/buildImageToImageSDXLGraph.ts index 5b8bc5d550..bbff0523f1 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/generation/buildImageToImageSDXLGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/generation/buildImageToImageSDXLGraph.ts @@ -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 => { const { bbox, params } = state.canvasV2; const { diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/generation/buildSD1Graph.ts b/invokeai/frontend/web/src/features/nodes/util/graph/generation/buildSD1Graph.ts index ddc8114c22..6966feef9e 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/generation/buildSD1Graph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/generation/buildSD1Graph.ts @@ -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 => { +export const buildSD1Graph = async (state: RootState, manager: CanvasManager): Promise => { const generationMode = manager.getGenerationMode(); const { bbox, params } = state.canvasV2; diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/generation/buildSDXLGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graph/generation/buildSDXLGraph.ts index f3eedd42c0..9177e9e745 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/generation/buildSDXLGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/generation/buildSDXLGraph.ts @@ -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 => { +export const buildSDXLGraph = async (state: RootState, manager: CanvasManager): Promise => { const generationMode = manager.getGenerationMode(); const { bbox, params } = state.canvasV2; diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/generation/buildTextToImageSD1SD2Graph.ts b/invokeai/frontend/web/src/features/nodes/util/graph/generation/buildTextToImageSD1SD2Graph.ts index 2c6f95b4fa..352c167863 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/generation/buildTextToImageSD1SD2Graph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/generation/buildTextToImageSD1SD2Graph.ts @@ -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 => { +export const buildTextToImageSD1SD2Graph = async (state: RootState, manager: CanvasManager): Promise => { const { bbox, params } = state.canvasV2; const {