From d5cd50c3ea3468f0fe5cb0ad72c50e02ded3370e Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Sun, 8 Sep 2024 17:24:21 +1000 Subject: [PATCH] feat(ui): split buffer renderer from object renderer --- .../CanvasEntityAdapterBase.ts | 6 + .../CanvasEntityAdapterControlLayer.ts | 5 +- .../CanvasEntityAdapterInpaintMask.ts | 5 +- .../CanvasEntityAdapterRasterLayer.ts | 5 +- .../CanvasEntityAdapterRegionalGuidance.ts | 5 +- .../konva/CanvasEntityBufferObjectRenderer.ts | 238 ++++++++++++++++++ .../konva/CanvasEntityFilterer.ts | 6 +- .../konva/CanvasEntityObjectRenderer.ts | 171 +------------ .../konva/CanvasEntityTransformer.ts | 6 +- .../konva/CanvasObjectBrushLine.ts | 5 +- .../konva/CanvasObjectEraserLine.ts | 5 +- .../controlLayers/konva/CanvasObjectImage.ts | 13 +- .../controlLayers/konva/CanvasObjectRect.ts | 5 +- .../controlLayers/konva/CanvasToolModule.ts | 90 ++++--- 14 files changed, 346 insertions(+), 219 deletions(-) create mode 100644 invokeai/frontend/web/src/features/controlLayers/konva/CanvasEntityBufferObjectRenderer.ts diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasEntityAdapter/CanvasEntityAdapterBase.ts b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasEntityAdapter/CanvasEntityAdapterBase.ts index 9a32cc3e4f..c4b0199832 100644 --- a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasEntityAdapter/CanvasEntityAdapterBase.ts +++ b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasEntityAdapter/CanvasEntityAdapterBase.ts @@ -1,6 +1,7 @@ import { createSelector } from '@reduxjs/toolkit'; import type { SerializableObject } from 'common/types'; import { deepClone } from 'common/util/deepClone'; +import type { CanvasEntityBufferObjectRenderer } from 'features/controlLayers/konva/CanvasEntityBufferObjectRenderer'; import type { CanvasEntityFilterer } from 'features/controlLayers/konva/CanvasEntityFilterer'; import type { CanvasEntityObjectRenderer } from 'features/controlLayers/konva/CanvasEntityObjectRenderer'; import type { CanvasEntityTransformer } from 'features/controlLayers/konva/CanvasEntityTransformer'; @@ -35,6 +36,11 @@ export abstract class CanvasEntityAdapterBase< */ abstract renderer: CanvasEntityObjectRenderer; + /** + * The buffer renderer for this entity adapter. All entities must have a buffer renderer. + */ + abstract bufferRenderer: CanvasEntityBufferObjectRenderer; + /** * The filterer for this entity adapter. Entities that support filtering should implement this property. */ diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasEntityAdapter/CanvasEntityAdapterControlLayer.ts b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasEntityAdapter/CanvasEntityAdapterControlLayer.ts index b5af1d4df3..df29569401 100644 --- a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasEntityAdapter/CanvasEntityAdapterControlLayer.ts +++ b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasEntityAdapter/CanvasEntityAdapterControlLayer.ts @@ -1,5 +1,6 @@ import type { SerializableObject } from 'common/types'; import { CanvasEntityAdapterBase } from 'features/controlLayers/konva/CanvasEntityAdapter/CanvasEntityAdapterBase'; +import { CanvasEntityBufferObjectRenderer } from 'features/controlLayers/konva/CanvasEntityBufferObjectRenderer'; import { CanvasEntityFilterer } from 'features/controlLayers/konva/CanvasEntityFilterer'; import { CanvasEntityObjectRenderer } from 'features/controlLayers/konva/CanvasEntityObjectRenderer'; import { CanvasEntityTransformer } from 'features/controlLayers/konva/CanvasEntityTransformer'; @@ -11,14 +12,16 @@ import { omit } from 'lodash-es'; export class CanvasEntityAdapterControlLayer extends CanvasEntityAdapterBase { static TYPE = 'control_layer_adapter'; - transformer: CanvasEntityTransformer; renderer: CanvasEntityObjectRenderer; + bufferRenderer: CanvasEntityBufferObjectRenderer; + transformer: CanvasEntityTransformer; filterer: CanvasEntityFilterer; constructor(entityIdentifier: CanvasEntityIdentifier<'control_layer'>, manager: CanvasManager) { super(entityIdentifier, manager, CanvasEntityAdapterControlLayer.TYPE); this.renderer = new CanvasEntityObjectRenderer(this); + this.bufferRenderer = new CanvasEntityBufferObjectRenderer(this); this.transformer = new CanvasEntityTransformer(this); this.filterer = new CanvasEntityFilterer(this); diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasEntityAdapter/CanvasEntityAdapterInpaintMask.ts b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasEntityAdapter/CanvasEntityAdapterInpaintMask.ts index 5d1b57c849..21cfd94db7 100644 --- a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasEntityAdapter/CanvasEntityAdapterInpaintMask.ts +++ b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasEntityAdapter/CanvasEntityAdapterInpaintMask.ts @@ -1,5 +1,6 @@ import type { SerializableObject } from 'common/types'; import { CanvasEntityAdapterBase } from 'features/controlLayers/konva/CanvasEntityAdapter/CanvasEntityAdapterBase'; +import { CanvasEntityBufferObjectRenderer } from 'features/controlLayers/konva/CanvasEntityBufferObjectRenderer'; import { CanvasEntityObjectRenderer } from 'features/controlLayers/konva/CanvasEntityObjectRenderer'; import { CanvasEntityTransformer } from 'features/controlLayers/konva/CanvasEntityTransformer'; import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager'; @@ -10,14 +11,16 @@ import { omit } from 'lodash-es'; export class CanvasEntityAdapterInpaintMask extends CanvasEntityAdapterBase { static TYPE = 'inpaint_mask_adapter'; - transformer: CanvasEntityTransformer; renderer: CanvasEntityObjectRenderer; + bufferRenderer: CanvasEntityBufferObjectRenderer; + transformer: CanvasEntityTransformer; filterer = undefined; constructor(entityIdentifier: CanvasEntityIdentifier<'inpaint_mask'>, manager: CanvasManager) { super(entityIdentifier, manager, CanvasEntityAdapterInpaintMask.TYPE); this.renderer = new CanvasEntityObjectRenderer(this); + this.bufferRenderer = new CanvasEntityBufferObjectRenderer(this); this.transformer = new CanvasEntityTransformer(this); this.subscriptions.add(this.manager.stateApi.createStoreSubscription(this.selectState, this.sync)); diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasEntityAdapter/CanvasEntityAdapterRasterLayer.ts b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasEntityAdapter/CanvasEntityAdapterRasterLayer.ts index 2333ebc031..8c5c56ed77 100644 --- a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasEntityAdapter/CanvasEntityAdapterRasterLayer.ts +++ b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasEntityAdapter/CanvasEntityAdapterRasterLayer.ts @@ -1,5 +1,6 @@ import type { SerializableObject } from 'common/types'; import { CanvasEntityAdapterBase } from 'features/controlLayers/konva/CanvasEntityAdapter/CanvasEntityAdapterBase'; +import { CanvasEntityBufferObjectRenderer } from 'features/controlLayers/konva/CanvasEntityBufferObjectRenderer'; import { CanvasEntityFilterer } from 'features/controlLayers/konva/CanvasEntityFilterer'; import { CanvasEntityObjectRenderer } from 'features/controlLayers/konva/CanvasEntityObjectRenderer'; import { CanvasEntityTransformer } from 'features/controlLayers/konva/CanvasEntityTransformer'; @@ -11,14 +12,16 @@ import { omit } from 'lodash-es'; export class CanvasEntityAdapterRasterLayer extends CanvasEntityAdapterBase { static TYPE = 'raster_layer_adapter'; - transformer: CanvasEntityTransformer; renderer: CanvasEntityObjectRenderer; + bufferRenderer: CanvasEntityBufferObjectRenderer; + transformer: CanvasEntityTransformer; filterer: CanvasEntityFilterer; constructor(entityIdentifier: CanvasEntityIdentifier<'raster_layer'>, manager: CanvasManager) { super(entityIdentifier, manager, CanvasEntityAdapterRasterLayer.TYPE); this.renderer = new CanvasEntityObjectRenderer(this); + this.bufferRenderer = new CanvasEntityBufferObjectRenderer(this); this.transformer = new CanvasEntityTransformer(this); this.filterer = new CanvasEntityFilterer(this); diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasEntityAdapter/CanvasEntityAdapterRegionalGuidance.ts b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasEntityAdapter/CanvasEntityAdapterRegionalGuidance.ts index 38d8065109..2044a2b0a6 100644 --- a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasEntityAdapter/CanvasEntityAdapterRegionalGuidance.ts +++ b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasEntityAdapter/CanvasEntityAdapterRegionalGuidance.ts @@ -1,5 +1,6 @@ import type { SerializableObject } from 'common/types'; import { CanvasEntityAdapterBase } from 'features/controlLayers/konva/CanvasEntityAdapter/CanvasEntityAdapterBase'; +import { CanvasEntityBufferObjectRenderer } from 'features/controlLayers/konva/CanvasEntityBufferObjectRenderer'; import { CanvasEntityObjectRenderer } from 'features/controlLayers/konva/CanvasEntityObjectRenderer'; import { CanvasEntityTransformer } from 'features/controlLayers/konva/CanvasEntityTransformer'; import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager'; @@ -10,14 +11,16 @@ import { omit } from 'lodash-es'; export class CanvasEntityAdapterRegionalGuidance extends CanvasEntityAdapterBase { static TYPE = 'regional_guidance_adapter'; - transformer: CanvasEntityTransformer; renderer: CanvasEntityObjectRenderer; + bufferRenderer: CanvasEntityBufferObjectRenderer; + transformer: CanvasEntityTransformer; filterer = undefined; constructor(entityIdentifier: CanvasEntityIdentifier<'regional_guidance'>, manager: CanvasManager) { super(entityIdentifier, manager, CanvasEntityAdapterRegionalGuidance.TYPE); this.renderer = new CanvasEntityObjectRenderer(this); + this.bufferRenderer = new CanvasEntityBufferObjectRenderer(this); this.transformer = new CanvasEntityTransformer(this); this.subscriptions.add(this.manager.stateApi.createStoreSubscription(this.selectState, this.sync)); diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasEntityBufferObjectRenderer.ts b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasEntityBufferObjectRenderer.ts new file mode 100644 index 0000000000..55762fe073 --- /dev/null +++ b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasEntityBufferObjectRenderer.ts @@ -0,0 +1,238 @@ +import { deepClone } from 'common/util/deepClone'; +import type { CanvasEntityAdapter } from 'features/controlLayers/konva/CanvasEntityAdapter/types'; +import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager'; +import { CanvasModuleBase } from 'features/controlLayers/konva/CanvasModuleBase'; +import { CanvasObjectBrushLine } from 'features/controlLayers/konva/CanvasObjectBrushLine'; +import { CanvasObjectEraserLine } from 'features/controlLayers/konva/CanvasObjectEraserLine'; +import { CanvasObjectImage } from 'features/controlLayers/konva/CanvasObjectImage'; +import { CanvasObjectRect } from 'features/controlLayers/konva/CanvasObjectRect'; +import { getPrefixedId } from 'features/controlLayers/konva/util'; +import type { + CanvasBrushLineState, + CanvasEraserLineState, + CanvasImageState, + CanvasRectState, +} from 'features/controlLayers/store/types'; +import Konva from 'konva'; +import type { Logger } from 'roarr'; +import { assert } from 'tsafe'; + +/** + * Union of all object renderers. + */ +type AnyObjectRenderer = CanvasObjectBrushLine | CanvasObjectEraserLine | CanvasObjectRect | CanvasObjectImage; +/** + * Union of all object states. + */ +type AnyObjectState = CanvasBrushLineState | CanvasEraserLineState | CanvasImageState | CanvasRectState; + +/** + * Handles rendering of objects for a canvas entity. + */ +export class CanvasEntityBufferObjectRenderer extends CanvasModuleBase { + readonly type = 'buffer_renderer'; + readonly id: string; + readonly path: string[]; + readonly parent: CanvasEntityAdapter; + readonly manager: CanvasManager; + readonly log: Logger; + + /** + * A set of subscriptions that should be cleaned up when the transformer is destroyed. + */ + subscriptions: Set<() => void> = new Set(); + + /** + * A buffer object state that is rendered separately from the other objects. This is used for objects that are being + * drawn in real-time, such as brush lines. The buffer object state only exists in this renderer and is not part of + * the application state until it is committed. + */ + state: AnyObjectState | null = null; + + /** + * The object renderer for the buffer object state. It is created when the buffer object state is set and destroyed + * when the buffer object state is cleared. This is separate from the other object renderers to allow the buffer to + * be rendered separately. + */ + renderer: AnyObjectRenderer | null = null; + + /** + * A object containing singleton Konva nodes. + */ + konva: { + /** + * A Konva Group that holds the buffer object renderer. + */ + group: Konva.Group; + }; + + constructor(parent: CanvasEntityAdapter) { + super(); + this.id = getPrefixedId(this.type); + this.parent = parent; + this.manager = parent.manager; + this.path = this.manager.buildPath(this); + this.log = this.manager.buildLogger(this); + this.log.debug('Creating module'); + + this.konva = { + group: new Konva.Group({ name: `${this.type}:buffer_group`, listening: false }), + }; + + this.parent.konva.layer.add(this.konva.group); + + // When switching tool, commit the buffer. This is necessary to prevent the buffer from being lost when the + // user switches tool mid-drawing, for example by pressing space to pan the stage. It's easy to press space + // to pan _before_ releasing the mouse button, which would cause the buffer to be lost if we didn't commit it. + this.subscriptions.add( + this.manager.tool.$tool.listen(() => { + if (this.hasBuffer() && !this.manager.$isBusy.get()) { + this.commitBuffer(); + } + }) + ); + } + + /** + * Renders the buffer object. If the buffer renderer does not exist, it will be created and its Konva group added to the + * parent entity's buffer object group. + * @returns A promise that resolves to a boolean, indicating if the object was rendered. + */ + renderBufferObject = async (): Promise => { + let didRender = false; + + if (!this.state) { + return false; + } + + if (this.state.type === 'brush_line') { + assert(this.renderer instanceof CanvasObjectBrushLine || !this.renderer); + + if (!this.renderer) { + this.renderer = new CanvasObjectBrushLine(this.state, this); + this.konva.group.add(this.renderer.konva.group); + } + + didRender = this.renderer.update(this.state, true); + } else if (this.state.type === 'eraser_line') { + assert(this.renderer instanceof CanvasObjectEraserLine || !this.renderer); + + if (!this.renderer) { + this.renderer = new CanvasObjectEraserLine(this.state, this); + this.konva.group.add(this.renderer.konva.group); + } + + didRender = this.renderer.update(this.state, true); + } else if (this.state.type === 'rect') { + assert(this.renderer instanceof CanvasObjectRect || !this.renderer); + + if (!this.renderer) { + this.renderer = new CanvasObjectRect(this.state, this); + this.konva.group.add(this.renderer.konva.group); + } + + didRender = this.renderer.update(this.state, true); + } else if (this.state.type === 'image') { + assert(this.renderer instanceof CanvasObjectImage || !this.renderer); + + if (!this.renderer) { + this.renderer = new CanvasObjectImage(this.state, this); + this.konva.group.add(this.renderer.konva.group); + } + didRender = await this.renderer.update(this.state, true); + } + + return didRender; + }; + + /** + * Determines if the renderer has a buffer object to render. + * @returns Whether the renderer has a buffer object to render. + */ + hasBuffer = (): boolean => { + return this.state !== null || this.renderer !== null; + }; + + /** + * Sets the buffer object state to render. + * @param objectState The object state to set as the buffer. + * @param resetBufferOffset Whether to reset the buffer's offset to 0,0. This is necessary when previewing filters. + * When previewing a filter, the buffer object is an image of the same size as the entity, so it should be rendered + * at the top-left corner of the entity. + * @returns A promise that resolves to a boolean, indicating if the object was rendered. + */ + setBuffer = async (objectState: AnyObjectState, resetBufferOffset: boolean = false): Promise => { + this.log.trace('Setting buffer'); + + this.state = objectState; + if (resetBufferOffset) { + this.konva.group.offset({ x: 0, y: 0 }); + } + return await this.renderBufferObject(); + }; + + /** + * Clears the buffer object state. + */ + clearBuffer = () => { + if (this.state || this.renderer) { + this.log.trace('Clearing buffer'); + this.renderer?.destroy(); + this.renderer = null; + this.state = null; + } + }; + + /** + * Commits the current buffer object, pushing the buffer object state back to the application state. + */ + commitBuffer = (options?: { pushToState?: boolean }) => { + const { pushToState } = { ...options, pushToState: true }; + + if (!this.state || !this.renderer) { + this.log.trace('No buffer to commit'); + return; + } + + this.log.trace('Committing buffer'); + + // Move the buffer to the persistent objects group/renderers + this.parent.renderer.adoptObjectRenderer(this.renderer); + + if (pushToState) { + const entityIdentifier = this.parent.entityIdentifier; + if (this.state.type === 'brush_line') { + this.manager.stateApi.addBrushLine({ entityIdentifier, brushLine: this.state }); + } else if (this.state.type === 'eraser_line') { + this.manager.stateApi.addEraserLine({ entityIdentifier, eraserLine: this.state }); + } else if (this.state.type === 'rect') { + this.manager.stateApi.addRect({ entityIdentifier, rect: this.state }); + } else { + this.log.warn({ buffer: this.state }, 'Invalid buffer object type'); + } + } + + this.renderer = null; + this.state = null; + }; + + destroy = () => { + this.log.debug('Destroying module'); + this.subscriptions.forEach((unsubscribe) => unsubscribe()); + this.subscriptions.clear(); + if (this.renderer) { + this.renderer.destroy(); + } + }; + + repr = () => { + return { + id: this.id, + type: this.type, + path: this.path, + parent: this.parent.id, + bufferState: deepClone(this.state), + bufferRenderer: this.renderer?.repr(), + }; + }; +} diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasEntityFilterer.ts b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasEntityFilterer.ts index 6b75e4565c..34b01c51de 100644 --- a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasEntityFilterer.ts +++ b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasEntityFilterer.ts @@ -96,7 +96,7 @@ export class CanvasEntityFilterer extends CanvasModuleBase { this.imageState = imageDTOToImageObject(imageDTO); - await this.parent.renderer.setBuffer(this.imageState, true); + await this.parent.bufferRenderer.setBuffer(this.imageState, true); this.parent.renderer.hideObjects(); this.$isProcessing.set(false); @@ -120,7 +120,7 @@ export class CanvasEntityFilterer extends CanvasModuleBase { return; } this.log.trace('Applying filter'); - this.parent.renderer.commitBuffer(); + this.parent.bufferRenderer.commitBuffer(); const rect = this.parent.transformer.getRelativeRect(); this.manager.stateApi.rasterizeEntity({ entityIdentifier: this.parent.entityIdentifier, @@ -142,7 +142,7 @@ export class CanvasEntityFilterer extends CanvasModuleBase { cancelFilter = () => { this.log.trace('Cancelling filter'); - this.parent.renderer.clearBuffer(); + this.parent.bufferRenderer.clearBuffer(); this.parent.renderer.showObjects(); this.parent.transformer.updatePosition(); this.parent.renderer.syncCache(true); diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasEntityObjectRenderer.ts b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasEntityObjectRenderer.ts index 75e3776c4f..95514bf191 100644 --- a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasEntityObjectRenderer.ts +++ b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasEntityObjectRenderer.ts @@ -68,19 +68,6 @@ export class CanvasEntityObjectRenderer extends CanvasModuleBase { */ subscriptions: Set<() => void> = new Set(); - /** - * A buffer object state that is rendered separately from the other objects. This is used for objects that are being - * drawn in real-time, such as brush lines. The buffer object state only exists in this renderer and is not part of - * the application state until it is committed. - */ - bufferState: AnyObjectState | null = null; - - /** - * The object renderer for the buffer object state. It is created when the buffer object state is set and destroyed - * when the buffer object state is cleared. This is separate from the other object renderers to allow the buffer to - * be rendered separately. - */ - bufferRenderer: AnyObjectRenderer | null = null; /** * A map of object renderers, keyed by their ID. * @@ -96,10 +83,6 @@ export class CanvasEntityObjectRenderer extends CanvasModuleBase { * A Konva Group that holds all the object renderers. */ objectGroup: Konva.Group; - /** - * A Konva Group that holds the buffer object renderer. - */ - bufferGroup: Konva.Group; /** * The compositing rect is used to draw the inpaint mask as a single shape with a given opacity. * @@ -143,12 +126,10 @@ export class CanvasEntityObjectRenderer extends CanvasModuleBase { this.konva = { objectGroup: new Konva.Group({ name: `${this.type}:object_group`, listening: false }), - bufferGroup: new Konva.Group({ name: `${this.type}:buffer_group`, listening: false }), compositing: null, }; this.parent.konva.layer.add(this.konva.objectGroup); - this.parent.konva.layer.add(this.konva.bufferGroup); if ( this.parent.entityIdentifier.type === 'inpaint_mask' || @@ -170,17 +151,6 @@ export class CanvasEntityObjectRenderer extends CanvasModuleBase { this.parent.konva.layer.add(this.konva.compositing.group); } - // When switching tool, commit the buffer. This is necessary to prevent the buffer from being lost when the - // user switches tool mid-drawing, for example by pressing space to pan the stage. It's easy to press space - // to pan _before_ releasing the mouse button, which would cause the buffer to be lost if we didn't commit it. - this.subscriptions.add( - this.manager.tool.$tool.listen(() => { - if (this.hasBuffer() && !this.manager.$isBusy.get()) { - this.commitBuffer(); - } - }) - ); - // The compositing rect must cover the whole stage at all times. When the stage is scaled, moved or resized, we // need to update the compositing rect to match the stage. this.subscriptions.add( @@ -227,6 +197,11 @@ export class CanvasEntityObjectRenderer extends CanvasModuleBase { return didRender; }; + adoptObjectRenderer = (renderer: AnyObjectRenderer) => { + this.renderers.set(renderer.id, renderer); + renderer.konva.group.moveTo(this.konva.objectGroup); + }; + syncCache = (force: boolean = false) => { if (this.renderers.size === 0) { this.log.trace('Clearing object group cache'); @@ -234,7 +209,7 @@ export class CanvasEntityObjectRenderer extends CanvasModuleBase { } else if (force || !this.konva.objectGroup.isCached()) { this.log.trace('Caching object group'); this.konva.objectGroup.clearCache(); - this.konva.objectGroup.cache({ pixelRatio: 1 }); + this.konva.objectGroup.cache({ pixelRatio: 1, imageSmoothingEnabled: false }); this.parent.renderer.updatePreviewCanvas(); } }; @@ -295,8 +270,8 @@ export class CanvasEntityObjectRenderer extends CanvasModuleBase { this.konva.compositing.group.opacity(opacity); } else { this.konva.objectGroup.opacity(opacity); - this.konva.bufferGroup.opacity(opacity); } + this.parent.bufferRenderer.konva.group.opacity(opacity); }; /** @@ -363,131 +338,6 @@ export class CanvasEntityObjectRenderer extends CanvasModuleBase { return didRender; }; - /** - * Renders the buffer object. If the buffer renderer does not exist, it will be created and its Konva group added to the - * parent entity's buffer object group. - * @returns A promise that resolves to a boolean, indicating if the object was rendered. - */ - renderBufferObject = async (): Promise => { - let didRender = false; - - if (!this.bufferState) { - return false; - } - - if (this.bufferState.type === 'brush_line') { - assert(this.bufferRenderer instanceof CanvasObjectBrushLine || !this.bufferRenderer); - - if (!this.bufferRenderer) { - this.bufferRenderer = new CanvasObjectBrushLine(this.bufferState, this); - this.konva.bufferGroup.add(this.bufferRenderer.konva.group); - } - - didRender = this.bufferRenderer.update(this.bufferState, true); - } else if (this.bufferState.type === 'eraser_line') { - assert(this.bufferRenderer instanceof CanvasObjectEraserLine || !this.bufferRenderer); - - if (!this.bufferRenderer) { - this.bufferRenderer = new CanvasObjectEraserLine(this.bufferState, this); - this.konva.bufferGroup.add(this.bufferRenderer.konva.group); - } - - didRender = this.bufferRenderer.update(this.bufferState, true); - } else if (this.bufferState.type === 'rect') { - assert(this.bufferRenderer instanceof CanvasObjectRect || !this.bufferRenderer); - - if (!this.bufferRenderer) { - this.bufferRenderer = new CanvasObjectRect(this.bufferState, this); - this.konva.bufferGroup.add(this.bufferRenderer.konva.group); - } - - didRender = this.bufferRenderer.update(this.bufferState, true); - } else if (this.bufferState.type === 'image') { - assert(this.bufferRenderer instanceof CanvasObjectImage || !this.bufferRenderer); - - if (!this.bufferRenderer) { - this.bufferRenderer = new CanvasObjectImage(this.bufferState, this); - this.konva.bufferGroup.add(this.bufferRenderer.konva.group); - } - didRender = await this.bufferRenderer.update(this.bufferState, true); - } - - return didRender; - }; - - /** - * Determines if the renderer has a buffer object to render. - * @returns Whether the renderer has a buffer object to render. - */ - hasBuffer = (): boolean => { - return this.bufferState !== null || this.bufferRenderer !== null; - }; - - /** - * Sets the buffer object state to render. - * @param objectState The object state to set as the buffer. - * @param resetBufferOffset Whether to reset the buffer's offset to 0,0. This is necessary when previewing filters. - * When previewing a filter, the buffer object is an image of the same size as the entity, so it should be rendered - * at the top-left corner of the entity. - * @returns A promise that resolves to a boolean, indicating if the object was rendered. - */ - setBuffer = async (objectState: AnyObjectState, resetBufferOffset: boolean = false): Promise => { - this.log.trace('Setting buffer'); - - this.bufferState = objectState; - if (resetBufferOffset) { - this.konva.bufferGroup.offset({ x: 0, y: 0 }); - } - return await this.renderBufferObject(); - }; - - /** - * Clears the buffer object state. - */ - clearBuffer = () => { - if (this.bufferState || this.bufferRenderer) { - this.log.trace('Clearing buffer'); - this.bufferRenderer?.destroy(); - this.bufferRenderer = null; - this.bufferState = null; - this.syncCache(true); - } - }; - - /** - * Commits the current buffer object, pushing the buffer object state back to the application state. - */ - commitBuffer = (options?: { pushToState?: boolean }) => { - const { pushToState } = { ...options, pushToState: true }; - - if (!this.bufferState || !this.bufferRenderer) { - this.log.trace('No buffer to commit'); - return; - } - - this.log.trace('Committing buffer'); - - // Move the buffer to the persistent objects group/renderers - this.bufferRenderer.konva.group.moveTo(this.konva.objectGroup); - this.renderers.set(this.bufferState.id, this.bufferRenderer); - - if (pushToState) { - const entityIdentifier = this.parent.entityIdentifier; - if (this.bufferState.type === 'brush_line') { - this.manager.stateApi.addBrushLine({ entityIdentifier, brushLine: this.bufferState }); - } else if (this.bufferState.type === 'eraser_line') { - this.manager.stateApi.addEraserLine({ entityIdentifier, eraserLine: this.bufferState }); - } else if (this.bufferState.type === 'rect') { - this.manager.stateApi.addRect({ entityIdentifier, rect: this.bufferState }); - } else { - this.log.warn({ buffer: this.bufferState }, 'Invalid buffer object type'); - } - } - - this.bufferRenderer = null; - this.bufferState = null; - }; - hideObjects = (except: string[] = []) => { for (const renderer of this.renderers.values()) { renderer.setVisibility(except.includes(renderer.id)); @@ -529,7 +379,7 @@ export class CanvasEntityObjectRenderer extends CanvasModuleBase { * @returns Whether the renderer has any objects to render. */ hasObjects = (): boolean => { - return this.renderers.size > 0 || this.bufferState !== null || this.bufferRenderer !== null; + return this.renderers.size > 0 || this.parent.bufferRenderer.hasBuffer(); }; /** @@ -576,8 +426,8 @@ export class CanvasEntityObjectRenderer extends CanvasModuleBase { }); const imageObject = imageDTOToImageObject(imageDTO); if (replaceObjects) { - await this.setBuffer(imageObject); - this.commitBuffer({ pushToState: false }); + await this.parent.bufferRenderer.setBuffer(imageObject); + this.parent.bufferRenderer.commitBuffer({ pushToState: false }); } this.manager.stateApi.rasterizeEntity({ entityIdentifier: this.parent.entityIdentifier, @@ -667,7 +517,6 @@ export class CanvasEntityObjectRenderer extends CanvasModuleBase { path: this.path, parent: this.parent.id, renderers: Array.from(this.renderers.values()).map((renderer) => renderer.repr()), - buffer: this.bufferRenderer?.repr(), }; }; } diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasEntityTransformer.ts b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasEntityTransformer.ts index 404b0e298f..adb6ba19c9 100644 --- a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasEntityTransformer.ts +++ b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasEntityTransformer.ts @@ -587,7 +587,7 @@ export class CanvasEntityTransformer extends CanvasModuleBase { rotation: 0, }; this.parent.renderer.konva.objectGroup.setAttrs(attrs); - this.parent.renderer.konva.bufferGroup.setAttrs(attrs); + this.parent.bufferRenderer.konva.group.setAttrs(attrs); this.konva.outlineRect.setAttrs(attrs); this.konva.proxyRect.setAttrs(attrs); }; @@ -608,7 +608,7 @@ export class CanvasEntityTransformer extends CanvasModuleBase { offsetY: pixelRect.y, }; this.parent.renderer.konva.objectGroup.setAttrs(groupAttrs); - this.parent.renderer.konva.bufferGroup.setAttrs(groupAttrs); + this.parent.bufferRenderer.konva.group.setAttrs(groupAttrs); this.update(position, pixelRect); }; @@ -669,7 +669,7 @@ export class CanvasEntityTransformer extends CanvasModuleBase { offsetY: pixelRect.y, }; this.parent.renderer.konva.objectGroup.setAttrs(groupAttrs); - this.parent.renderer.konva.bufferGroup.setAttrs(groupAttrs); + this.parent.bufferRenderer.konva.group.setAttrs(groupAttrs); } this.parent.renderer.updatePreviewCanvas(); diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasObjectBrushLine.ts b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasObjectBrushLine.ts index eee77083fd..0900741319 100644 --- a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasObjectBrushLine.ts +++ b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasObjectBrushLine.ts @@ -1,5 +1,6 @@ import { rgbaColorToString } from 'common/util/colorCodeTransformers'; import { deepClone } from 'common/util/deepClone'; +import type { CanvasEntityBufferObjectRenderer } from 'features/controlLayers/konva/CanvasEntityBufferObjectRenderer'; import type { CanvasEntityObjectRenderer } from 'features/controlLayers/konva/CanvasEntityObjectRenderer'; import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager'; import { CanvasModuleBase } from 'features/controlLayers/konva/CanvasModuleBase'; @@ -11,7 +12,7 @@ export class CanvasObjectBrushLine extends CanvasModuleBase { readonly type = 'object_brush_line'; readonly id: string; readonly path: string[]; - readonly parent: CanvasEntityObjectRenderer; + readonly parent: CanvasEntityObjectRenderer | CanvasEntityBufferObjectRenderer; readonly manager: CanvasManager; readonly log: Logger; @@ -21,7 +22,7 @@ export class CanvasObjectBrushLine extends CanvasModuleBase { line: Konva.Line; }; - constructor(state: CanvasBrushLineState, parent: CanvasEntityObjectRenderer) { + constructor(state: CanvasBrushLineState, parent: CanvasEntityObjectRenderer | CanvasEntityBufferObjectRenderer) { super(); const { id, clip } = state; this.id = id; diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasObjectEraserLine.ts b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasObjectEraserLine.ts index e4de8522bd..da62ccfb0c 100644 --- a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasObjectEraserLine.ts +++ b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasObjectEraserLine.ts @@ -1,4 +1,5 @@ import { deepClone } from 'common/util/deepClone'; +import type { CanvasEntityBufferObjectRenderer } from 'features/controlLayers/konva/CanvasEntityBufferObjectRenderer'; import type { CanvasEntityObjectRenderer } from 'features/controlLayers/konva/CanvasEntityObjectRenderer'; import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager'; import { CanvasModuleBase } from 'features/controlLayers/konva/CanvasModuleBase'; @@ -10,7 +11,7 @@ export class CanvasObjectEraserLine extends CanvasModuleBase { readonly type = 'object_eraser_line'; readonly id: string; readonly path: string[]; - readonly parent: CanvasEntityObjectRenderer; + readonly parent: CanvasEntityObjectRenderer | CanvasEntityBufferObjectRenderer; readonly manager: CanvasManager; readonly log: Logger; @@ -20,7 +21,7 @@ export class CanvasObjectEraserLine extends CanvasModuleBase { line: Konva.Line; }; - constructor(state: CanvasEraserLineState, parent: CanvasEntityObjectRenderer) { + constructor(state: CanvasEraserLineState, parent: CanvasEntityObjectRenderer | CanvasEntityBufferObjectRenderer) { super(); this.id = state.id; this.parent = parent; diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasObjectImage.ts b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasObjectImage.ts index 4c237878e3..ef7361afd1 100644 --- a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasObjectImage.ts +++ b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasObjectImage.ts @@ -1,5 +1,6 @@ import { Mutex } from 'async-mutex'; import { deepClone } from 'common/util/deepClone'; +import type { CanvasEntityBufferObjectRenderer } from 'features/controlLayers/konva/CanvasEntityBufferObjectRenderer'; import type { CanvasEntityFilterer } from 'features/controlLayers/konva/CanvasEntityFilterer'; import type { CanvasEntityObjectRenderer } from 'features/controlLayers/konva/CanvasEntityObjectRenderer'; import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager'; @@ -16,7 +17,11 @@ export class CanvasObjectImage extends CanvasModuleBase { readonly type = 'object_image'; readonly id: string; readonly path: string[]; - readonly parent: CanvasEntityObjectRenderer | CanvasStagingAreaModule | CanvasEntityFilterer; + readonly parent: + | CanvasEntityObjectRenderer + | CanvasEntityBufferObjectRenderer + | CanvasStagingAreaModule + | CanvasEntityFilterer; readonly manager: CanvasManager; readonly log: Logger; @@ -33,7 +38,11 @@ export class CanvasObjectImage extends CanvasModuleBase { constructor( state: CanvasImageState, - parent: CanvasEntityObjectRenderer | CanvasStagingAreaModule | CanvasEntityFilterer + parent: + | CanvasEntityObjectRenderer + | CanvasEntityBufferObjectRenderer + | CanvasStagingAreaModule + | CanvasEntityFilterer ) { super(); this.id = state.id; diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasObjectRect.ts b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasObjectRect.ts index d8224c25bd..6a0194d33a 100644 --- a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasObjectRect.ts +++ b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasObjectRect.ts @@ -1,5 +1,6 @@ import { rgbaColorToString } from 'common/util/colorCodeTransformers'; import { deepClone } from 'common/util/deepClone'; +import type { CanvasEntityBufferObjectRenderer } from 'features/controlLayers/konva/CanvasEntityBufferObjectRenderer'; import type { CanvasEntityObjectRenderer } from 'features/controlLayers/konva/CanvasEntityObjectRenderer'; import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager'; import { CanvasModuleBase } from 'features/controlLayers/konva/CanvasModuleBase'; @@ -11,7 +12,7 @@ export class CanvasObjectRect extends CanvasModuleBase { readonly type = 'object_rect'; readonly id: string; readonly path: string[]; - readonly parent: CanvasEntityObjectRenderer; + readonly parent: CanvasEntityObjectRenderer | CanvasEntityBufferObjectRenderer; readonly manager: CanvasManager; readonly log: Logger; @@ -22,7 +23,7 @@ export class CanvasObjectRect extends CanvasModuleBase { }; isFirstRender: boolean = false; - constructor(state: CanvasRectState, parent: CanvasEntityObjectRenderer) { + constructor(state: CanvasRectState, parent: CanvasEntityObjectRenderer | CanvasEntityBufferObjectRenderer) { super(); this.id = state.id; this.parent = parent; diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasToolModule.ts b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasToolModule.ts index 373d9feffc..49136b12d6 100644 --- a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasToolModule.ts +++ b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasToolModule.ts @@ -307,15 +307,15 @@ export class CanvasToolModule extends CanvasModuleBase { return; } - if (selectedEntity.renderer.bufferState?.type !== 'rect' && selectedEntity.renderer.hasBuffer()) { - selectedEntity.renderer.commitBuffer(); + if (selectedEntity.bufferRenderer.state?.type !== 'rect' && selectedEntity.bufferRenderer.hasBuffer()) { + selectedEntity.bufferRenderer.commitBuffer(); return; } if (tool === 'brush') { const normalizedPoint = offsetCoord(cursorPos, selectedEntity.state.position); const alignedPoint = alignCoordForTool(normalizedPoint, settings.brushWidth); - await selectedEntity.renderer.setBuffer({ + await selectedEntity.bufferRenderer.setBuffer({ id: getPrefixedId('brush_line'), type: 'brush_line', points: [alignedPoint.x, alignedPoint.y], @@ -329,10 +329,10 @@ export class CanvasToolModule extends CanvasModuleBase { if (tool === 'eraser') { const normalizedPoint = offsetCoord(cursorPos, selectedEntity.state.position); const alignedPoint = alignCoordForTool(normalizedPoint, settings.brushWidth); - if (selectedEntity.renderer.bufferState && selectedEntity.renderer.hasBuffer()) { - selectedEntity.renderer.commitBuffer(); + if (selectedEntity.bufferRenderer.state && selectedEntity.bufferRenderer.hasBuffer()) { + selectedEntity.bufferRenderer.commitBuffer(); } - await selectedEntity.renderer.setBuffer({ + await selectedEntity.bufferRenderer.setBuffer({ id: getPrefixedId('eraser_line'), type: 'eraser_line', points: [alignedPoint.x, alignedPoint.y], @@ -380,11 +380,11 @@ export class CanvasToolModule extends CanvasModuleBase { const alignedPoint = alignCoordForTool(normalizedPoint, settings.brushWidth); if (e.evt.shiftKey && lastLinePoint) { // Create a straight line from the last line point - if (selectedEntity.renderer.hasBuffer()) { - selectedEntity.renderer.commitBuffer(); + if (selectedEntity.bufferRenderer.hasBuffer()) { + selectedEntity.bufferRenderer.commitBuffer(); } - await selectedEntity.renderer.setBuffer({ + await selectedEntity.bufferRenderer.setBuffer({ id: getPrefixedId('brush_line'), type: 'brush_line', points: [ @@ -399,10 +399,10 @@ export class CanvasToolModule extends CanvasModuleBase { clip: this.getClip(selectedEntity.state), }); } else { - if (selectedEntity.renderer.hasBuffer()) { - selectedEntity.renderer.commitBuffer(); + if (selectedEntity.bufferRenderer.hasBuffer()) { + selectedEntity.bufferRenderer.commitBuffer(); } - await selectedEntity.renderer.setBuffer({ + await selectedEntity.bufferRenderer.setBuffer({ id: getPrefixedId('brush_line'), type: 'brush_line', points: [alignedPoint.x, alignedPoint.y], @@ -418,10 +418,10 @@ export class CanvasToolModule extends CanvasModuleBase { const alignedPoint = alignCoordForTool(normalizedPoint, settings.eraserWidth); if (e.evt.shiftKey && lastLinePoint) { // Create a straight line from the last line point - if (selectedEntity.renderer.hasBuffer()) { - selectedEntity.renderer.commitBuffer(); + if (selectedEntity.bufferRenderer.hasBuffer()) { + selectedEntity.bufferRenderer.commitBuffer(); } - await selectedEntity.renderer.setBuffer({ + await selectedEntity.bufferRenderer.setBuffer({ id: getPrefixedId('eraser_line'), type: 'eraser_line', points: [ @@ -435,10 +435,10 @@ export class CanvasToolModule extends CanvasModuleBase { clip: this.getClip(selectedEntity.state), }); } else { - if (selectedEntity.renderer.hasBuffer()) { - selectedEntity.renderer.commitBuffer(); + if (selectedEntity.bufferRenderer.hasBuffer()) { + selectedEntity.bufferRenderer.commitBuffer(); } - await selectedEntity.renderer.setBuffer({ + await selectedEntity.bufferRenderer.setBuffer({ id: getPrefixedId('eraser_line'), type: 'eraser_line', points: [alignedPoint.x, alignedPoint.y], @@ -449,10 +449,10 @@ export class CanvasToolModule extends CanvasModuleBase { } if (tool === 'rect') { - if (selectedEntity.renderer.hasBuffer()) { - selectedEntity.renderer.commitBuffer(); + if (selectedEntity.bufferRenderer.hasBuffer()) { + selectedEntity.bufferRenderer.commitBuffer(); } - await selectedEntity.renderer.setBuffer({ + await selectedEntity.bufferRenderer.setBuffer({ id: getPrefixedId('rect'), type: 'rect', rect: { x: Math.round(normalizedPoint.x), y: Math.round(normalizedPoint.y), width: 0, height: 0 }, @@ -483,26 +483,32 @@ export class CanvasToolModule extends CanvasModuleBase { const tool = this.$tool.get(); if (tool === 'brush') { - if (selectedEntity.renderer.bufferState?.type === 'brush_line' && selectedEntity.renderer.hasBuffer()) { - selectedEntity.renderer.commitBuffer(); + if ( + selectedEntity.bufferRenderer.state?.type === 'brush_line' && + selectedEntity.bufferRenderer.hasBuffer() + ) { + selectedEntity.bufferRenderer.commitBuffer(); } else { - selectedEntity.renderer.clearBuffer(); + selectedEntity.bufferRenderer.clearBuffer(); } } if (tool === 'eraser') { - if (selectedEntity.renderer.bufferState?.type === 'eraser_line' && selectedEntity.renderer.hasBuffer()) { - selectedEntity.renderer.commitBuffer(); + if ( + selectedEntity.bufferRenderer.state?.type === 'eraser_line' && + selectedEntity.bufferRenderer.hasBuffer() + ) { + selectedEntity.bufferRenderer.commitBuffer(); } else { - selectedEntity.renderer.clearBuffer(); + selectedEntity.bufferRenderer.clearBuffer(); } } if (tool === 'rect') { - if (selectedEntity.renderer.bufferState?.type === 'rect' && selectedEntity.renderer.hasBuffer()) { - selectedEntity.renderer.commitBuffer(); + if (selectedEntity.bufferRenderer.state?.type === 'rect' && selectedEntity.bufferRenderer.hasBuffer()) { + selectedEntity.bufferRenderer.commitBuffer(); } else { - selectedEntity.renderer.clearBuffer(); + selectedEntity.bufferRenderer.clearBuffer(); } } } finally { @@ -535,7 +541,7 @@ export class CanvasToolModule extends CanvasModuleBase { return; } - const bufferState = selectedEntity.renderer.bufferState; + const bufferState = selectedEntity.bufferRenderer.state; if (!bufferState) { return; @@ -559,7 +565,7 @@ export class CanvasToolModule extends CanvasModuleBase { } bufferState.points.push(alignedPoint.x, alignedPoint.y); - await selectedEntity.renderer.setBuffer(bufferState); + await selectedEntity.bufferRenderer.setBuffer(bufferState); } else if (tool === 'eraser' && bufferState.type === 'eraser_line') { const lastPoint = getLastPointOfLine(bufferState.points); const minDistance = settings.eraserWidth * this.config.BRUSH_SPACING_TARGET_SCALE; @@ -576,15 +582,15 @@ export class CanvasToolModule extends CanvasModuleBase { } bufferState.points.push(alignedPoint.x, alignedPoint.y); - await selectedEntity.renderer.setBuffer(bufferState); + await selectedEntity.bufferRenderer.setBuffer(bufferState); } else if (tool === 'rect' && bufferState.type === 'rect') { const normalizedPoint = offsetCoord(cursorPos, selectedEntity.state.position); const alignedPoint = floorCoord(normalizedPoint); bufferState.rect.width = Math.round(alignedPoint.x - bufferState.rect.x); bufferState.rect.height = Math.round(alignedPoint.y - bufferState.rect.y); - await selectedEntity.renderer.setBuffer(bufferState); + await selectedEntity.bufferRenderer.setBuffer(bufferState); } else { - selectedEntity?.renderer.clearBuffer(); + selectedEntity?.bufferRenderer.clearBuffer(); } } finally { this.render(); @@ -599,8 +605,12 @@ export class CanvasToolModule extends CanvasModuleBase { this.$cursorPos.set(null); const selectedEntity = this.manager.stateApi.getSelectedEntityAdapter(); - if (selectedEntity && selectedEntity.renderer.bufferState?.type !== 'rect' && selectedEntity.renderer.hasBuffer()) { - selectedEntity.renderer.commitBuffer(); + if ( + selectedEntity && + selectedEntity.bufferRenderer.state?.type !== 'rect' && + selectedEntity.bufferRenderer.hasBuffer() + ) { + selectedEntity.bufferRenderer.commitBuffer(); } this.render(); @@ -640,8 +650,8 @@ export class CanvasToolModule extends CanvasModuleBase { this.$isMouseDown.set(false); const selectedEntity = this.manager.stateApi.getSelectedEntityAdapter(); - if (selectedEntity && selectedEntity.renderer.hasBuffer() && !this.manager.$isBusy.get()) { - selectedEntity.renderer.commitBuffer(); + if (selectedEntity && selectedEntity.bufferRenderer.hasBuffer() && !this.manager.$isBusy.get()) { + selectedEntity.bufferRenderer.commitBuffer(); } }; @@ -659,7 +669,7 @@ export class CanvasToolModule extends CanvasModuleBase { e.preventDefault(); const selectedEntity = this.manager.stateApi.getSelectedEntityAdapter(); if (selectedEntity) { - selectedEntity.renderer.clearBuffer(); + selectedEntity.bufferRenderer.clearBuffer(); } return; }