diff --git a/invokeai/frontend/web/src/features/controlLayers/components/ControlLayersToolbar.tsx b/invokeai/frontend/web/src/features/controlLayers/components/ControlLayersToolbar.tsx index 3818167eec..05a7ae638f 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/ControlLayersToolbar.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/ControlLayersToolbar.tsx @@ -1,6 +1,6 @@ /* eslint-disable i18next/no-literal-string */ import { Button } from '@chakra-ui/react'; -import { Flex } from '@invoke-ai/ui-library'; +import { Flex, Switch } from '@invoke-ai/ui-library'; import { useStore } from '@nanostores/react'; import { useAppSelector } from 'app/store/storeHooks'; import { BrushWidth } from 'features/controlLayers/components/BrushWidth'; @@ -14,6 +14,7 @@ import { UndoRedoButtonGroup } from 'features/controlLayers/components/UndoRedoB import { $canvasManager } from 'features/controlLayers/konva/CanvasManager'; import { ToggleProgressButton } from 'features/gallery/components/ImageViewer/ToggleProgressButton'; import { ViewerToggleMenu } from 'features/gallery/components/ImageViewer/ViewerToggleMenu'; +import type { ChangeEvent } from 'react'; import { memo, useCallback } from 'react'; export const ControlLayersToolbar = memo(() => { @@ -27,12 +28,19 @@ export const ControlLayersToolbar = memo(() => { l.calculateBbox(); } }, [canvasManager]); - const debug = useCallback(() => { - if (!canvasManager) { - return; - } - canvasManager.logDebugInfo(); - }, [canvasManager]); + const onChangeDebugging = useCallback( + (e: ChangeEvent) => { + if (!canvasManager) { + return; + } + if (e.target.checked) { + canvasManager.enableDebugging(); + } else { + canvasManager.disableDebugging(); + } + }, + [canvasManager] + ); return ( @@ -46,7 +54,7 @@ export const ControlLayersToolbar = memo(() => { {tool === 'eraser' && } - + debug diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasBrushLine.ts b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasBrushLine.ts index a35b8bf12b..c052a55775 100644 --- a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasBrushLine.ts +++ b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasBrushLine.ts @@ -25,7 +25,7 @@ export class CanvasBrushLine { this.id = id; this.parent = parent; - this.parent.log.trace(`Creating brush line ${this.id}`); + this.parent._log.trace(`Creating brush line ${this.id}`); this.konva = { group: new Konva.Group({ @@ -54,7 +54,7 @@ export class CanvasBrushLine { async update(state: BrushLine, force?: boolean): Promise { if (force || this.state !== state) { - this.parent.log.trace(`Updating brush line ${this.id}`); + this.parent._log.trace(`Updating brush line ${this.id}`); const { points, color, clip, strokeWidth } = state; this.konva.line.setAttrs({ // A line with only one point will not be rendered, so we duplicate the points to make it visible @@ -71,7 +71,7 @@ export class CanvasBrushLine { } destroy() { - this.parent.log.trace(`Destroying brush line ${this.id}`); + this.parent._log.trace(`Destroying brush line ${this.id}`); this.konva.group.destroy(); } } diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasEraserLine.ts b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasEraserLine.ts index 910426506b..a1d8d19311 100644 --- a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasEraserLine.ts +++ b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasEraserLine.ts @@ -26,7 +26,7 @@ export class CanvasEraserLine { this.id = id; this.parent = parent; - this.parent.log.trace(`Creating eraser line ${this.id}`); + this.parent._log.trace(`Creating eraser line ${this.id}`); this.konva = { group: new Konva.Group({ @@ -55,7 +55,7 @@ export class CanvasEraserLine { async update(state: EraserLine, force?: boolean): Promise { if (force || this.state !== state) { - this.parent.log.trace(`Updating eraser line ${this.id}`); + this.parent._log.trace(`Updating eraser line ${this.id}`); const { points, clip, strokeWidth } = state; this.konva.line.setAttrs({ // A line with only one point will not be rendered, so we duplicate the points to make it visible @@ -71,7 +71,7 @@ export class CanvasEraserLine { } destroy() { - this.parent.log.trace(`Destroying eraser line ${this.id}`); + this.parent._log.trace(`Destroying eraser line ${this.id}`); this.konva.group.destroy(); } } diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasImage.ts b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasImage.ts index a0f801f2b0..f88ecfd93b 100644 --- a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasImage.ts +++ b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasImage.ts @@ -36,7 +36,7 @@ export class CanvasImage { this.id = id; this.parent = parent; - this.parent.log.trace(`Creating image ${this.id}`); + this.parent._log.trace(`Creating image ${this.id}`); this.konva = { group: new Konva.Group({ name: CanvasImage.GROUP_NAME, listening: false, x, y }), @@ -77,13 +77,13 @@ export class CanvasImage { async updateImageSource(imageName: string) { try { - this.parent.log.trace(`Updating image source ${this.id}`); + this.parent._log.trace(`Updating image source ${this.id}`); this.isLoading = true; this.konva.group.visible(true); if (!this.image) { - this.konva.placeholder.group.visible(true); + this.konva.placeholder.group.visible(false); this.konva.placeholder.text.text(t('common.loadingImage', 'Loading Image')); } @@ -130,7 +130,7 @@ export class CanvasImage { async update(state: ImageObject, force?: boolean): Promise { if (this.state !== state || force) { - this.parent.log.trace(`Updating image ${this.id}`); + this.parent._log.trace(`Updating image ${this.id}`); const { width, height, x, y, image, filters } = state; if (this.state.image.name !== image.name || force) { @@ -154,7 +154,7 @@ export class CanvasImage { } destroy() { - this.parent.log.trace(`Destroying image ${this.id}`); + this.parent._log.trace(`Destroying image ${this.id}`); this.konva.group.destroy(); } } diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasLayer.ts b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasLayer.ts index dd5b690395..670fa7a1b8 100644 --- a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasLayer.ts +++ b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasLayer.ts @@ -24,24 +24,6 @@ import { uploadImage } from 'services/api/endpoints/images'; import { assert } from 'tsafe'; import { v4 as uuidv4 } from 'uuid'; -const getCenter = (rect: Rect): Coordinate => { - return { - x: rect.x + rect.width / 2, - y: rect.y + rect.height / 2, - }; -}; - -window.getCenter = getCenter; - -function rotatePoint(point: Coordinate, origin: Coordinate, deg: number): Coordinate { - const angle = deg * (Math.PI / 180); // Convert to radians - const rotatedX = Math.cos(angle) * (point.x - origin.x) - Math.sin(angle) * (point.y - origin.y) + origin.x; - const rotatedY = Math.sin(angle) * (point.x - origin.x) + Math.cos(angle) * (point.y - origin.y) + origin.y; - - return { x: rotatedX, y: rotatedY }; -} - -window.rotatePoint = rotatePoint; export class CanvasLayer { static NAME_PREFIX = 'layer'; static LAYER_NAME = `${CanvasLayer.NAME_PREFIX}_layer`; @@ -51,8 +33,8 @@ export class CanvasLayer { static OBJECT_GROUP_NAME = `${CanvasLayer.NAME_PREFIX}_object-group`; static BBOX_NAME = `${CanvasLayer.NAME_PREFIX}_bbox`; - private drawingBuffer: BrushLine | EraserLine | RectShape | null; - private state: LayerEntity; + _drawingBuffer: BrushLine | EraserLine | RectShape | null; + _state: LayerEntity; id: string; manager: CanvasManager; @@ -66,10 +48,11 @@ export class CanvasLayer { }; objects: Map; - log: Logger; - bboxNeedsUpdate: boolean; + _log: Logger; + _bboxNeedsUpdate: boolean; + _isFirstRender: boolean; + isTransforming: boolean; - isFirstRender: boolean; rect: Rect; bbox: Rect; @@ -113,17 +96,7 @@ export class CanvasLayer { this.konva.layer.add(this.konva.bbox); this.konva.transformer.on('transformstart', () => { - console.log('>>> transformstart'); - console.log('interactionRect', { - x: this.konva.interactionRect.x(), - y: this.konva.interactionRect.y(), - scaleX: this.konva.interactionRect.scaleX(), - scaleY: this.konva.interactionRect.scaleY(), - width: this.konva.interactionRect.width(), - height: this.konva.interactionRect.height(), - }); - this.logBbox('transformstart bbox'); - console.log('this.state.position', this.state.position); + this.logDebugInfo("'transformstart' fired"); }); this.konva.transformer.on('transform', () => { @@ -152,17 +125,7 @@ export class CanvasLayer { // this.konva.interactionRect.scaleY(scaleY); // this.konva.interactionRect.rotation(0); - console.log('>>> transform'); - console.log('activeAnchor', this.konva.transformer.getActiveAnchor()); - console.log('interactionRect', { - x: this.konva.interactionRect.x(), - y: this.konva.interactionRect.y(), - scaleX: this.konva.interactionRect.scaleX(), - scaleY: this.konva.interactionRect.scaleY(), - width: this.konva.interactionRect.width(), - height: this.konva.interactionRect.height(), - rotation: this.konva.interactionRect.rotation(), - }); + this.logDebugInfo("'transform' fired"); this.konva.objectGroup.setAttrs({ x: this.konva.interactionRect.x(), @@ -171,33 +134,10 @@ export class CanvasLayer { scaleY: this.konva.interactionRect.scaleY(), rotation: this.konva.interactionRect.rotation(), }); - - console.log('objectGroup', { - x: this.konva.objectGroup.x(), - y: this.konva.objectGroup.y(), - scaleX: this.konva.objectGroup.scaleX(), - scaleY: this.konva.objectGroup.scaleY(), - offsetX: this.konva.objectGroup.offsetX(), - offsetY: this.konva.objectGroup.offsetY(), - width: this.konva.objectGroup.width(), - height: this.konva.objectGroup.height(), - rotation: this.konva.objectGroup.rotation(), - }); }); this.konva.transformer.on('transformend', () => { - // this.offsetX = this.konva.interactionRect.x() - this.state.position.x; - // this.offsetY = this.konva.interactionRect.y() - this.state.position.y; - // this.width = Math.round(this.konva.interactionRect.width() * this.konva.interactionRect.scaleX()); - // this.height = Math.round(this.konva.interactionRect.height() * this.konva.interactionRect.scaleY()); - // this.manager.stateApi.onPosChanged( - // { - // id: this.id, - // position: { x: this.konva.objectGroup.x(), y: this.konva.objectGroup.y() }, - // }, - // 'layer' - // ); - this.logBbox('transformend bbox'); + this.logDebugInfo("'transformend' fired"); }); this.konva.interactionRect.on('dragmove', () => { @@ -220,7 +160,7 @@ export class CanvasLayer { }); }); this.konva.interactionRect.on('dragend', () => { - this.logBbox('dragend bbox'); + this.logDebugInfo("'dragend' fired"); if (this.isTransforming) { // When the user cancels the transformation, we need to reset the layer, so we should not update the layer's @@ -241,38 +181,38 @@ export class CanvasLayer { }); this.objects = new Map(); - this.drawingBuffer = null; - this.state = state; + this._drawingBuffer = null; + this._state = state; this.rect = this.getDefaultRect(); this.bbox = this.getDefaultRect(); - this.bboxNeedsUpdate = true; + this._bboxNeedsUpdate = true; this.isTransforming = false; - this.isFirstRender = true; - this.log = this.manager.getLogger(`layer_${this.id}`); + this._isFirstRender = true; + this._log = this.manager.getLogger(`layer_${this.id}`); } destroy(): void { - this.log.debug(`Layer ${this.id} - destroying`); + this._log.debug(`Layer ${this.id} - destroying`); this.konva.layer.destroy(); } getDrawingBuffer() { - return this.drawingBuffer; + return this._drawingBuffer; } async setDrawingBuffer(obj: BrushLine | EraserLine | RectShape | null) { if (obj) { - this.drawingBuffer = obj; - await this._renderObject(this.drawingBuffer, true); + this._drawingBuffer = obj; + await this._renderObject(this._drawingBuffer, true); } else { - this.drawingBuffer = null; + this._drawingBuffer = null; } } async finalizeDrawingBuffer() { - if (!this.drawingBuffer) { + if (!this._drawingBuffer) { return; } - const drawingBuffer = this.drawingBuffer; + const drawingBuffer = this._drawingBuffer; this.setDrawingBuffer(null); // We need to give the objects a fresh ID else they will be considered the same object when they are re-rendered as @@ -291,44 +231,50 @@ export class CanvasLayer { } async update(arg?: { state: LayerEntity; toolState: CanvasV2State['tool']; isSelected: boolean }) { - const state = get(arg, 'state', this.state); + const state = get(arg, 'state', this._state); const toolState = get(arg, 'toolState', this.manager.stateApi.getToolState()); const isSelected = get(arg, 'isSelected', this.manager.stateApi.getIsSelected(this.id)); - if (!this.isFirstRender && state === this.state) { - this.log.trace('State unchanged, skipping update'); + if (!this._isFirstRender && state === this._state) { + this._log.trace('State unchanged, skipping update'); return; } - this.log.debug('Updating'); + this._log.debug('Updating'); const { position, objects, opacity, isEnabled } = state; - if (this.isFirstRender || position !== this.state.position) { + if (this._isFirstRender || position !== this._state.position) { await this.updatePosition({ position }); } - if (this.isFirstRender || objects !== this.state.objects) { + if (this._isFirstRender || objects !== this._state.objects) { await this.updateObjects({ objects }); } - if (this.isFirstRender || opacity !== this.state.opacity) { + if (this._isFirstRender || opacity !== this._state.opacity) { await this.updateOpacity({ opacity }); } - if (this.isFirstRender || isEnabled !== this.state.isEnabled) { + if (this._isFirstRender || isEnabled !== this._state.isEnabled) { await this.updateVisibility({ isEnabled }); } await this.updateInteraction({ toolState, isSelected }); - this.state = state; + + if (this._isFirstRender) { + await this.updateBbox(); + } + + this._state = state; + this._isFirstRender = false; } async updateVisibility(arg?: { isEnabled: boolean }) { - this.log.trace('Updating visibility'); - const isEnabled = get(arg, 'isEnabled', this.state.isEnabled); - const hasObjects = this.objects.size > 0 || this.drawingBuffer !== null; + this._log.trace('Updating visibility'); + const isEnabled = get(arg, 'isEnabled', this._state.isEnabled); + const hasObjects = this.objects.size > 0 || this._drawingBuffer !== null; this.konva.layer.visible(isEnabled || hasObjects); } async updatePosition(arg?: { position: Coordinate }) { - this.log.trace('Updating position'); - const position = get(arg, 'position', this.state.position); + this._log.trace('Updating position'); + const position = get(arg, 'position', this._state.position); const bboxPadding = this.manager.getScaledBboxPadding(); this.konva.objectGroup.setAttrs({ @@ -348,9 +294,9 @@ export class CanvasLayer { } async updateObjects(arg?: { objects: LayerEntity['objects'] }) { - this.log.trace('Updating objects'); + this._log.trace('Updating objects'); - const objects = get(arg, 'objects', this.state.objects); + const objects = get(arg, 'objects', this._state.objects); const objectIds = objects.map(mapId); @@ -358,7 +304,7 @@ export class CanvasLayer { // Destroy any objects that are no longer in state for (const object of this.objects.values()) { - if (!objectIds.includes(object.id) && object.id !== this.drawingBuffer?.id) { + if (!objectIds.includes(object.id) && object.id !== this._drawingBuffer?.id) { this.objects.delete(object.id); object.destroy(); didUpdate = true; @@ -371,8 +317,8 @@ export class CanvasLayer { } } - if (this.drawingBuffer) { - if (await this._renderObject(this.drawingBuffer)) { + if (this._drawingBuffer) { + if (await this._renderObject(this._drawingBuffer)) { didUpdate = true; } } @@ -383,15 +329,15 @@ export class CanvasLayer { } async updateOpacity(arg?: { opacity: number }) { - this.log.trace('Updating opacity'); + this._log.trace('Updating opacity'); - const opacity = get(arg, 'opacity', this.state.opacity); + const opacity = get(arg, 'opacity', this._state.opacity); this.konva.objectGroup.opacity(opacity); } async updateInteraction(arg?: { toolState: CanvasV2State['tool']; isSelected: boolean }) { - this.log.trace('Updating interaction'); + this._log.trace('Updating interaction'); const toolState = get(arg, 'toolState', this.manager.stateApi.getToolState()); const isSelected = get(arg, 'isSelected', this.manager.stateApi.getIsSelected(this.id)); @@ -440,42 +386,49 @@ export class CanvasLayer { } async updateBbox() { - this.log.trace('Updating bbox'); + this._log.trace('Updating bbox'); // If the bbox has no width or height, that means the layer is fully transparent. This can happen if it is only - // eraser lines, fully clipped brush lines or if it has been fully erased. In this case, we should reset the layer - // so we aren't drawing shapes that do not render anything. + // eraser lines, fully clipped brush lines or if it has been fully erased. if (this.bbox.width === 0 || this.bbox.height === 0) { - this.manager.stateApi.onEntityReset({ id: this.id }, 'layer'); + if (this.objects.size > 0) { + // The layer is fully transparent but has objects - reset it + this.manager.stateApi.onEntityReset({ id: this.id }, 'layer'); + } + this.konva.bbox.visible(false); + this.konva.interactionRect.visible(false); return; } + this.konva.bbox.visible(true); + this.konva.interactionRect.visible(true); + const onePixel = this.manager.getScaledPixel(); const bboxPadding = this.manager.getScaledBboxPadding(); this.konva.bbox.setAttrs({ - x: this.state.position.x + this.bbox.x - bboxPadding, - y: this.state.position.y + this.bbox.y - bboxPadding, + x: this._state.position.x + this.bbox.x - bboxPadding, + y: this._state.position.y + this.bbox.y - bboxPadding, width: this.bbox.width + bboxPadding * 2, height: this.bbox.height + bboxPadding * 2, strokeWidth: onePixel, }); this.konva.interactionRect.setAttrs({ - x: this.state.position.x + this.bbox.x, - y: this.state.position.y + this.bbox.y, + x: this._state.position.x + this.bbox.x, + y: this._state.position.y + this.bbox.y, width: this.bbox.width, height: this.bbox.height, }); this.konva.objectGroup.setAttrs({ - x: this.state.position.x + this.bbox.x, - y: this.state.position.y + this.bbox.y, + x: this._state.position.x + this.bbox.x, + y: this._state.position.y + this.bbox.y, offsetX: this.bbox.x, offsetY: this.bbox.y, }); } async syncStageScale() { - this.log.trace('Syncing scale to stage'); + this._log.trace('Syncing scale to stage'); const onePixel = this.manager.getScaledPixel(); const bboxPadding = this.manager.getScaledBboxPadding(); @@ -552,7 +505,7 @@ export class CanvasLayer { } async startTransform() { - this.log.debug('Starting transform'); + this._log.debug('Starting transform'); this.isTransforming = true; // When transforming, we want the stage to still be movable if the view tool is selected. If the transformer or @@ -583,14 +536,15 @@ export class CanvasLayer { } async applyTransform() { - this.log.debug('Applying transform'); + this._log.debug('Applying transform'); - this.isTransforming = false; const objectGroupClone = this.konva.objectGroup.clone(); const interactionRectClone = this.konva.interactionRect.clone(); const rect = interactionRectClone.getClientRect(); const blob = await konvaNodeToBlob(objectGroupClone, rect); - previewBlob(blob, 'transformed layer'); + if (this.manager._isDebugging) { + previewBlob(blob, 'transformed layer'); + } const imageDTO = await uploadImage(blob, `${this.id}_transform.png`, 'other', true); const { dispatch } = getStore(); dispatch(layerRasterized({ id: this.id, imageDTO, position: { x: rect.x, y: rect.y } })); @@ -599,11 +553,11 @@ export class CanvasLayer { } async cancelTransform() { - this.log.debug('Canceling transform'); + this._log.debug('Canceling transform'); this.isTransforming = false; this.resetScale(); - await this.updatePosition({ position: this.state.position }); + await this.updatePosition({ position: this._state.position }); await this.updateBbox(); await this.updateInteraction({ toolState: this.manager.stateApi.getToolState(), @@ -616,7 +570,7 @@ export class CanvasLayer { } calculateBbox = debounce(() => { - this.log.debug('Calculating bbox'); + this._log.debug('Calculating bbox'); if (this.objects.size === 0) { this.rect = this.getDefaultRect(); @@ -625,7 +579,6 @@ export class CanvasLayer { return; } - let needsPixelBbox = false; const rect = this.konva.objectGroup.getClientRect({ skipTransform: true }); /** @@ -640,6 +593,7 @@ export class CanvasLayer { * TODO(psyche): Using pixel data is slow. Is it possible to be clever and somehow subtract the eraser lines and * clipped areas from the client rect? */ + let needsPixelBbox = false; for (const obj of this.objects.values()) { const isEraserLine = obj instanceof CanvasEraserLine; const isImage = obj instanceof CanvasImage; @@ -653,7 +607,7 @@ export class CanvasLayer { if (!needsPixelBbox) { this.rect = deepClone(rect); this.bbox = deepClone(rect); - this.log.trace({ bbox: this.bbox, rect: this.rect }, 'Got bbox from client rect'); + this._log.trace({ bbox: this.bbox, rect: this.rect }, 'Got bbox from client rect'); this.updateBbox(); return; } @@ -682,19 +636,42 @@ export class CanvasLayer { } else { this.bbox = deepClone(rect); } - this.log.trace({ bbox: this.bbox, rect: this.rect, extents }, `Got bbox from worker`); + this._log.trace({ bbox: this.bbox, rect: this.rect, extents }, `Got bbox from worker`); this.updateBbox(); clone.destroy(); } ); }, CanvasManager.BBOX_DEBOUNCE_MS); - logBbox(msg: string = 'bbox') { - console.log(msg, { - x: this.state.position.x, - y: this.state.position.y, - rect: deepClone(this.rect), - bbox: deepClone(this.bbox), - }); + logDebugInfo(msg = 'Debug info') { + const debugInfo = { + id: this.id, + state: this._state, + rect: this.rect, + bbox: this.bbox, + objects: Array.from(this.objects.values()).map((obj) => obj.id), + isTransforming: this.isTransforming, + interactionRectAttrs: { + x: this.konva.interactionRect.x(), + y: this.konva.interactionRect.y(), + scaleX: this.konva.interactionRect.scaleX(), + scaleY: this.konva.interactionRect.scaleY(), + width: this.konva.interactionRect.width(), + height: this.konva.interactionRect.height(), + rotation: this.konva.interactionRect.rotation(), + }, + objectGroupAttrs: { + x: this.konva.objectGroup.x(), + y: this.konva.objectGroup.y(), + scaleX: this.konva.objectGroup.scaleX(), + scaleY: this.konva.objectGroup.scaleY(), + width: this.konva.objectGroup.width(), + height: this.konva.objectGroup.height(), + rotation: this.konva.objectGroup.rotation(), + offsetX: this.konva.objectGroup.offsetX(), + offsetY: this.konva.objectGroup.offsetY(), + }, + }; + this._log.debug(debugInfo, msg); } } diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasManager.ts b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasManager.ts index cb5954c619..d1e0fbee6b 100644 --- a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasManager.ts +++ b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasManager.ts @@ -64,7 +64,7 @@ type Util = { export const $canvasManager = atom(null); export class CanvasManager { - private static BBOX_PADDING_PX = 5; + static BBOX_PADDING_PX = 5; static BBOX_DEBOUNCE_MS = 300; stage: Konva.Stage; @@ -82,13 +82,15 @@ export class CanvasManager { log: Logger; workerLog: Logger; + _isDebugging: boolean; + onTransform: ((isTransforming: boolean) => void) | null; - private store: Store; - private isFirstRender: boolean; - private prevState: CanvasV2State; - private worker: Worker; - private tasks: Map void }>; + _store: Store; + _isFirstRender: boolean; + _prevState: CanvasV2State; + _worker: Worker; + _tasks: Map void }>; constructor( stage: Konva.Stage, @@ -99,10 +101,10 @@ export class CanvasManager { ) { this.stage = stage; this.container = container; - this.store = store; - this.stateApi = new CanvasStateApi(this.store); - this.prevState = this.stateApi.getState(); - this.isFirstRender = true; + this._store = store; + this.stateApi = new CanvasStateApi(this._store); + this._prevState = this.stateApi.getState(); + this._isFirstRender = true; this.log = logger('canvas'); this.workerLog = logger('worker'); @@ -133,9 +135,9 @@ export class CanvasManager { this.initialImage = new CanvasInitialImage(this.stateApi.getInitialImageState(), this); this.stage.add(this.initialImage.konva.layer); - this.worker = new Worker(new URL('./worker.ts', import.meta.url), { type: 'module', name: 'worker' }); - this.tasks = new Map(); - this.worker.onmessage = (event: MessageEvent) => { + this._worker = new Worker(new URL('./worker.ts', import.meta.url), { type: 'module', name: 'worker' }); + this._tasks = new Map(); + this._worker.onmessage = (event: MessageEvent) => { const { type, data } = event.data; if (type === 'log') { if (data.ctx) { @@ -144,20 +146,30 @@ export class CanvasManager { this.workerLog[data.level](data.message); } } else if (type === 'extents') { - const task = this.tasks.get(data.id); + const task = this._tasks.get(data.id); if (!task) { return; } task.onComplete(data.extents); } }; - this.worker.onerror = (event) => { + this._worker.onerror = (event) => { this.log.error({ message: event.message }, 'Worker error'); }; - this.worker.onmessageerror = () => { + this._worker.onmessageerror = () => { this.log.error('Worker message error'); }; this.onTransform = null; + this._isDebugging = false; + } + + enableDebugging() { + this._isDebugging = true; + this.logDebugInfo(); + } + + disableDebugging() { + this._isDebugging = false; } getLogger(namespace: string) { @@ -171,8 +183,8 @@ export class CanvasManager { type: 'get_bbox', data: { ...data, id }, }; - this.tasks.set(id, { task, onComplete }); - this.worker.postMessage(task, [data.buffer]); + this._tasks.set(id, { task, onComplete }); + this._worker.postMessage(task, [data.buffer]); } async renderInitialImage() { @@ -306,12 +318,12 @@ export class CanvasManager { render = async () => { const state = this.stateApi.getState(); - if (this.prevState === state && !this.isFirstRender) { + if (this._prevState === state && !this._isFirstRender) { this.log.trace('No changes detected, skipping render'); return; } - if (this.isFirstRender || state.layers.entities !== this.prevState.layers.entities) { + if (this._isFirstRender || state.layers.entities !== this._prevState.layers.entities) { this.log.debug('Rendering layers'); for (const canvasLayer of this.layers.values()) { @@ -339,9 +351,9 @@ export class CanvasManager { } if ( - this.isFirstRender || - state.tool.selected !== this.prevState.tool.selected || - state.selectedEntityIdentifier?.id !== this.prevState.selectedEntityIdentifier?.id + this._isFirstRender || + state.tool.selected !== this._prevState.tool.selected || + state.selectedEntityIdentifier?.id !== this._prevState.selectedEntityIdentifier?.id ) { this.log.debug('Updating interaction'); for (const layer of this.layers.values()) { @@ -350,89 +362,89 @@ export class CanvasManager { } if ( - this.isFirstRender || - state.initialImage !== this.prevState.initialImage || - state.bbox.rect !== this.prevState.bbox.rect || - state.tool.selected !== this.prevState.tool.selected || - state.selectedEntityIdentifier?.id !== this.prevState.selectedEntityIdentifier?.id + this._isFirstRender || + state.initialImage !== this._prevState.initialImage || + state.bbox.rect !== this._prevState.bbox.rect || + state.tool.selected !== this._prevState.tool.selected || + state.selectedEntityIdentifier?.id !== this._prevState.selectedEntityIdentifier?.id ) { this.log.debug('Rendering initial image'); await this.renderInitialImage(); } if ( - this.isFirstRender || - state.regions.entities !== this.prevState.regions.entities || - state.settings.maskOpacity !== this.prevState.settings.maskOpacity || - state.tool.selected !== this.prevState.tool.selected || - state.selectedEntityIdentifier?.id !== this.prevState.selectedEntityIdentifier?.id + this._isFirstRender || + state.regions.entities !== this._prevState.regions.entities || + state.settings.maskOpacity !== this._prevState.settings.maskOpacity || + state.tool.selected !== this._prevState.tool.selected || + state.selectedEntityIdentifier?.id !== this._prevState.selectedEntityIdentifier?.id ) { this.log.debug('Rendering regions'); await this.renderRegions(); } if ( - this.isFirstRender || - state.inpaintMask !== this.prevState.inpaintMask || - state.settings.maskOpacity !== this.prevState.settings.maskOpacity || - state.tool.selected !== this.prevState.tool.selected || - state.selectedEntityIdentifier?.id !== this.prevState.selectedEntityIdentifier?.id + this._isFirstRender || + state.inpaintMask !== this._prevState.inpaintMask || + state.settings.maskOpacity !== this._prevState.settings.maskOpacity || + state.tool.selected !== this._prevState.tool.selected || + state.selectedEntityIdentifier?.id !== this._prevState.selectedEntityIdentifier?.id ) { this.log.debug('Rendering inpaint mask'); await this.renderInpaintMask(); } if ( - this.isFirstRender || - state.controlAdapters.entities !== this.prevState.controlAdapters.entities || - state.tool.selected !== this.prevState.tool.selected || - state.selectedEntityIdentifier?.id !== this.prevState.selectedEntityIdentifier?.id + this._isFirstRender || + state.controlAdapters.entities !== this._prevState.controlAdapters.entities || + state.tool.selected !== this._prevState.tool.selected || + state.selectedEntityIdentifier?.id !== this._prevState.selectedEntityIdentifier?.id ) { this.log.debug('Rendering control adapters'); await this.renderControlAdapters(); } if ( - this.isFirstRender || - state.bbox !== this.prevState.bbox || - state.tool.selected !== this.prevState.tool.selected || - state.session.isActive !== this.prevState.session.isActive + this._isFirstRender || + state.bbox !== this._prevState.bbox || + state.tool.selected !== this._prevState.tool.selected || + state.session.isActive !== this._prevState.session.isActive ) { this.log.debug('Rendering generation bbox'); await this.preview.bbox.render(); } if ( - this.isFirstRender || - state.layers !== this.prevState.layers || - state.controlAdapters !== this.prevState.controlAdapters || - state.regions !== this.prevState.regions + this._isFirstRender || + state.layers !== this._prevState.layers || + state.controlAdapters !== this._prevState.controlAdapters || + state.regions !== this._prevState.regions ) { // this.log.debug('Updating entity bboxes'); // debouncedUpdateBboxes(stage, canvasV2.layers, canvasV2.controlAdapters, canvasV2.regions, onBboxChanged); } - if (this.isFirstRender || state.session !== this.prevState.session) { + if (this._isFirstRender || state.session !== this._prevState.session) { this.log.debug('Rendering staging area'); await this.preview.stagingArea.render(); } if ( - this.isFirstRender || - state.layers.entities !== this.prevState.layers.entities || - state.controlAdapters.entities !== this.prevState.controlAdapters.entities || - state.regions.entities !== this.prevState.regions.entities || - state.inpaintMask !== this.prevState.inpaintMask || - state.selectedEntityIdentifier?.id !== this.prevState.selectedEntityIdentifier?.id + this._isFirstRender || + state.layers.entities !== this._prevState.layers.entities || + state.controlAdapters.entities !== this._prevState.controlAdapters.entities || + state.regions.entities !== this._prevState.regions.entities || + state.inpaintMask !== this._prevState.inpaintMask || + state.selectedEntityIdentifier?.id !== this._prevState.selectedEntityIdentifier?.id ) { this.log.debug('Arranging entities'); await this.arrangeEntities(); } - this.prevState = state; + this._prevState = state; - if (this.isFirstRender) { - this.isFirstRender = false; + if (this._isFirstRender) { + this._isFirstRender = false; } }; @@ -448,7 +460,7 @@ export class CanvasManager { resizeObserver.observe(this.container); this.fitStageToContainer(); - const unsubscribeRenderer = this.store.subscribe(this.render); + const unsubscribeRenderer = this._store.subscribe(this.render); // When we this flag, we need to render the staging area $shouldShowStagedImage.subscribe(async (shouldShowStagedImage, prevShouldShowStagedImage) => { diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasRect.ts b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasRect.ts index 042fb1e644..47386717b1 100644 --- a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasRect.ts +++ b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasRect.ts @@ -26,7 +26,7 @@ export class CanvasRect { this.id = id; this.parent = parent; - this.parent.log.trace(`Creating rect ${this.id}`); + this.parent._log.trace(`Creating rect ${this.id}`); this.konva = { group: new Konva.Group({ name: CanvasRect.GROUP_NAME, listening: false }), @@ -47,7 +47,7 @@ export class CanvasRect { async update(state: RectShape, force?: boolean): Promise { if (this.state !== state || force) { - this.parent.log.trace(`Updating rect ${this.id}`); + this.parent._log.trace(`Updating rect ${this.id}`); const { x, y, width, height, color } = state; this.konva.rect.setAttrs({ x, @@ -64,7 +64,7 @@ export class CanvasRect { } destroy() { - this.parent.log.trace(`Destroying rect ${this.id}`); + this.parent._log.trace(`Destroying rect ${this.id}`); this.konva.group.destroy(); } }