From 5dcef6fa0d312ad3a1d2989d47013c04d1205076 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Mon, 5 Aug 2024 22:01:06 +1000 Subject: [PATCH] feat(ui): wip inpaint mask uses new API --- .../controlLayers/konva/CanvasInpaintMask.ts | 336 +++++------------- .../controlLayers/konva/CanvasManager.ts | 34 +- .../konva/CanvasObjectRenderer.ts | 69 +++- .../controlLayers/konva/CanvasTransformer.ts | 7 +- .../features/controlLayers/konva/events.ts | 8 +- 5 files changed, 177 insertions(+), 277 deletions(-) diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasInpaintMask.ts b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasInpaintMask.ts index cede2c9928..d2ce4518df 100644 --- a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasInpaintMask.ts +++ b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasInpaintMask.ts @@ -1,294 +1,128 @@ -import { rgbColorToString } from 'common/util/colorCodeTransformers'; -import { CanvasBrushLineRenderer } from 'features/controlLayers/konva/CanvasBrushLine'; -import { CanvasEraserLineRenderer } from 'features/controlLayers/konva/CanvasEraserLine'; import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager'; -import { CanvasRectRenderer } from 'features/controlLayers/konva/CanvasRect'; -import { getNodeBboxFast } from 'features/controlLayers/konva/entityBbox'; -import { mapId } from 'features/controlLayers/konva/util'; -import type { - CanvasBrushLineState, - CanvasEraserLineState, - CanvasInpaintMaskState, - CanvasRectState, -} from 'features/controlLayers/store/types'; -import { isDrawingTool, RGBA_RED } from 'features/controlLayers/store/types'; +import { CanvasObjectRenderer } from 'features/controlLayers/konva/CanvasObjectRenderer'; +import { CanvasTransformer } from 'features/controlLayers/konva/CanvasTransformer'; +import type { CanvasInpaintMaskState, CanvasV2State, GetLoggingContext } from 'features/controlLayers/store/types'; import Konva from 'konva'; +import { get } from 'lodash-es'; +import type { Logger } from 'roarr'; import { assert } from 'tsafe'; export class CanvasInpaintMask { - static NAME_PREFIX = 'inpaint-mask'; - static LAYER_NAME = `${CanvasInpaintMask.NAME_PREFIX}_layer`; - static TRANSFORMER_NAME = `${CanvasInpaintMask.NAME_PREFIX}_transformer`; - static GROUP_NAME = `${CanvasInpaintMask.NAME_PREFIX}_group`; - static OBJECT_GROUP_NAME = `${CanvasInpaintMask.NAME_PREFIX}_object-group`; - static COMPOSITING_RECT_NAME = `${CanvasInpaintMask.NAME_PREFIX}_compositing-rect`; static TYPE = 'inpaint_mask' as const; - private drawingBuffer: CanvasBrushLineState | CanvasEraserLineState | CanvasRectState | null; - private state: CanvasInpaintMaskState; + static NAME_PREFIX = 'inpaint-mask'; + static KONVA_LAYER_NAME = `${CanvasInpaintMask.NAME_PREFIX}_layer`; + static OBJECT_GROUP_NAME = `${CanvasInpaintMask.NAME_PREFIX}_object-group`; id = CanvasInpaintMask.TYPE; type = CanvasInpaintMask.TYPE; manager: CanvasManager; + log: Logger; + getLoggingContext: GetLoggingContext; + + state: CanvasInpaintMaskState; + + transformer: CanvasTransformer; + renderer: CanvasObjectRenderer; + + isFirstRender: boolean = true; konva: { layer: Konva.Layer; - group: Konva.Group; objectGroup: Konva.Group; - transformer: Konva.Transformer; - compositingRect: Konva.Rect; }; - objects: Map; constructor(state: CanvasInpaintMaskState, manager: CanvasManager) { this.manager = manager; + this.getLoggingContext = this.manager.buildGetLoggingContext(this); + this.log = this.manager.buildLogger(this.getLoggingContext); + this.log.debug({ state }, 'Creating inpaint mask'); this.konva = { - layer: new Konva.Layer({ name: CanvasInpaintMask.LAYER_NAME }), - group: new Konva.Group({ name: CanvasInpaintMask.GROUP_NAME, listening: false }), - objectGroup: new Konva.Group({ name: CanvasInpaintMask.OBJECT_GROUP_NAME, listening: false }), - transformer: new Konva.Transformer({ - name: CanvasInpaintMask.TRANSFORMER_NAME, - shouldOverdrawWholeArea: true, - draggable: true, - dragDistance: 0, - enabledAnchors: ['top-left', 'top-right', 'bottom-left', 'bottom-right'], - rotateEnabled: false, - flipEnabled: false, + layer: new Konva.Layer({ + name: CanvasInpaintMask.KONVA_LAYER_NAME, + listening: false, + imageSmoothingEnabled: false, }), - compositingRect: new Konva.Rect({ name: CanvasInpaintMask.COMPOSITING_RECT_NAME, listening: false }), + objectGroup: new Konva.Group({ name: CanvasInpaintMask.OBJECT_GROUP_NAME, listening: false }), }; - this.konva.group.add(this.konva.objectGroup); - this.konva.layer.add(this.konva.group); + this.transformer = new CanvasTransformer(this); + this.renderer = new CanvasObjectRenderer(this, true); + assert(this.renderer.konva.compositingRect, 'Compositing rect must be set'); - this.konva.transformer.on('transformend', () => { - this.manager.stateApi.onScaleChanged( - { - id: this.id, - scale: this.konva.group.scaleX(), - position: { x: this.konva.group.x(), y: this.konva.group.y() }, - }, - 'inpaint_mask' - ); - }); - this.konva.transformer.on('dragend', () => { - this.manager.stateApi.setEntityPosition( - { id: this.id, position: { x: this.konva.group.x(), y: this.konva.group.y() } }, - 'inpaint_mask' - ); - }); - this.konva.layer.add(this.konva.transformer); + this.konva.layer.add(this.konva.objectGroup); + this.konva.layer.add(this.renderer.konva.compositingRect); + this.konva.layer.add(...this.transformer.getNodes()); - this.konva.group.add(this.konva.compositingRect); - this.objects = new Map(); - this.drawingBuffer = null; this.state = state; } - destroy(): void { + destroy = (): void => { + this.log.debug('Destroying inpaint mask'); + // We need to call the destroy method on all children so they can do their own cleanup. + this.transformer.destroy(); + this.renderer.destroy(); this.konva.layer.destroy(); - } + }; - getDrawingBuffer() { - return this.drawingBuffer; - } + update = async (arg?: { state: CanvasInpaintMaskState; toolState: CanvasV2State['tool']; isSelected: boolean }) => { + const state = get(arg, 'state', this.state); - async setDrawingBuffer(obj: CanvasBrushLineState | CanvasEraserLineState | CanvasRectState | null) { - this.drawingBuffer = obj; - if (this.drawingBuffer) { - if (this.drawingBuffer.type === 'brush_line') { - this.drawingBuffer.color = RGBA_RED; - } else if (this.drawingBuffer.type === 'rect') { - this.drawingBuffer.color = RGBA_RED; - } - - await this.renderObject(this.drawingBuffer, true); - this.updateGroup(true); - } - } - - finalizeDrawingBuffer() { - if (!this.drawingBuffer) { + if (!this.isFirstRender && state === this.state) { + this.log.trace('State unchanged, skipping update'); return; } - if (this.drawingBuffer.type === 'brush_line') { - this.manager.stateApi.addBrushLine({ id: this.id, brushLine: this.drawingBuffer }, 'inpaint_mask'); - } else if (this.drawingBuffer.type === 'eraser_line') { - this.manager.stateApi.addEraserLine({ id: this.id, eraserLine: this.drawingBuffer }, 'inpaint_mask'); - } else if (this.drawingBuffer.type === 'rect') { - this.manager.stateApi.addRect({ id: this.id, rect: this.drawingBuffer }, 'inpaint_mask'); - } - this.setDrawingBuffer(null); - } - async render(state: CanvasInpaintMaskState) { + // const maskOpacity = this.manager.stateApi.getMaskOpacity() + + this.log.debug('Updating'); + const { position, objects, isEnabled } = state; + + if (this.isFirstRender || objects !== this.state.objects) { + await this.updateObjects({ objects }); + } + if (this.isFirstRender || position !== this.state.position) { + await this.transformer.updatePosition({ position }); + } + // if (this.isFirstRender || opacity !== this.state.opacity) { + // await this.updateOpacity({ opacity }); + // } + if (this.isFirstRender || isEnabled !== this.state.isEnabled) { + await this.updateVisibility({ isEnabled }); + } + // this.transformer.syncInteractionState(); + + if (this.isFirstRender) { + await this.transformer.updateBbox(); + } + this.state = state; + this.isFirstRender = false; + }; - // Update the layer's position and listening state - this.konva.group.setAttrs({ - x: state.position.x, - y: state.position.y, - scaleX: 1, - scaleY: 1, - }); + updateObjects = async (arg?: { objects: CanvasInpaintMaskState['objects'] }) => { + this.log.trace('Updating objects'); - let didDraw = false; + const objects = get(arg, 'objects', this.state.objects); - const objectIds = state.objects.map(mapId); - // Destroy any objects that are no longer in state - for (const object of this.objects.values()) { - if (!objectIds.includes(object.id)) { - this.objects.delete(object.id); - object.destroy(); - didDraw = true; - } + const didUpdate = await this.renderer.render(objects); + + if (didUpdate) { + this.transformer.requestRectCalculation(); } - for (const obj of state.objects) { - if (await this.renderObject(obj)) { - didDraw = true; - } - } + this.isFirstRender = false; + }; - if (this.drawingBuffer) { - if (await this.renderObject(this.drawingBuffer)) { - didDraw = true; - } - } + // updateOpacity = (arg?: { opacity: number }) => { + // this.log.trace('Updating opacity'); + // const opacity = get(arg, 'opacity', this.state.opacity); + // this.konva.objectGroup.opacity(opacity); + // }; - this.updateGroup(didDraw); - } - - private async renderObject(obj: CanvasInpaintMaskState['objects'][number], force = false): Promise { - if (obj.type === 'brush_line') { - let brushLine = this.objects.get(obj.id); - assert(brushLine instanceof CanvasBrushLineRenderer || brushLine === undefined); - - if (!brushLine) { - brushLine = new CanvasBrushLineRenderer(obj); - this.objects.set(brushLine.id, brushLine); - this.konva.objectGroup.add(brushLine.konva.group); - return true; - } else { - if (brushLine.update(obj, force)) { - return true; - } - } - } else if (obj.type === 'eraser_line') { - let eraserLine = this.objects.get(obj.id); - assert(eraserLine instanceof CanvasEraserLineRenderer || eraserLine === undefined); - - if (!eraserLine) { - eraserLine = new CanvasEraserLineRenderer(obj); - this.objects.set(eraserLine.id, eraserLine); - this.konva.objectGroup.add(eraserLine.konva.group); - return true; - } else { - if (eraserLine.update(obj, force)) { - return true; - } - } - } else if (obj.type === 'rect') { - let rect = this.objects.get(obj.id); - assert(rect instanceof CanvasRectRenderer || rect === undefined); - - if (!rect) { - rect = new CanvasRectRenderer(obj); - this.objects.set(rect.id, rect); - this.konva.objectGroup.add(rect.konva.group); - return true; - } else { - if (rect.update(obj, force)) { - return true; - } - } - } - - return false; - } - - updateGroup(didDraw: boolean) { - this.konva.layer.visible(this.state.isEnabled); - - // The user is allowed to reduce mask opacity to 0, but we need the opacity for the compositing rect to work - this.konva.group.opacity(1); - - if (didDraw) { - // Convert the color to a string, stripping the alpha - the object group will handle opacity. - const rgbColor = rgbColorToString(this.state.fill); - const maskOpacity = this.manager.stateApi.getMaskOpacity(); - - this.konva.compositingRect.setAttrs({ - // The rect should be the size of the layer - use the fast method if we don't have a pixel-perfect bbox already - ...getNodeBboxFast(this.konva.objectGroup), - fill: rgbColor, - opacity: maskOpacity, - // Draw this rect only where there are non-transparent pixels under it (e.g. the mask shapes) - globalCompositeOperation: 'source-in', - visible: true, - // This rect must always be on top of all other shapes - zIndex: this.objects.size + 1, - }); - } - - const isSelected = this.manager.stateApi.getIsSelected(this.id); - const selectedTool = this.manager.stateApi.getToolState().selected; - - if (this.objects.size === 0) { - // If the layer is totally empty, reset the cache and bail out. - this.konva.layer.listening(false); - this.konva.transformer.nodes([]); - if (this.konva.group.isCached()) { - this.konva.group.clearCache(); - } - return; - } - - if (isSelected && selectedTool === 'move') { - // When the layer is selected and being moved, we should always cache it. - // We should update the cache if we drew to the layer. - if (!this.konva.group.isCached() || didDraw) { - // this.konva.group.cache(); - } - // Activate the transformer - this.konva.layer.listening(true); - this.konva.transformer.nodes([this.konva.group]); - this.konva.transformer.forceUpdate(); - return; - } - - if (isSelected && selectedTool !== 'move') { - // If the layer is selected but not using the move tool, we don't want the layer to be listening. - this.konva.layer.listening(false); - // The transformer also does not need to be active. - this.konva.transformer.nodes([]); - if (isDrawingTool(selectedTool)) { - // We are using a drawing tool (brush, eraser, rect). These tools change the layer's rendered appearance, so we - // should never be cached. - if (this.konva.group.isCached()) { - this.konva.group.clearCache(); - } - } else { - // We are using a non-drawing tool (move, view, bbox), so we should cache the layer. - // We should update the cache if we drew to the layer. - if (!this.konva.group.isCached() || didDraw) { - // this.konva.group.cache(); - } - } - return; - } - - if (!isSelected) { - // Unselected layers should not be listening - this.konva.layer.listening(false); - // The transformer also does not need to be active. - this.konva.transformer.nodes([]); - // Update the layer's cache if it's not already cached or we drew to it. - if (!this.konva.group.isCached() || didDraw) { - // this.konva.group.cache(); - } - - return; - } - } + updateVisibility = (arg?: { isEnabled: boolean }) => { + this.log.trace('Updating visibility'); + const isEnabled = get(arg, 'isEnabled', this.state.isEnabled); + this.konva.layer.visible(isEnabled && this.renderer.hasObjects()); + }; } diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasManager.ts b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasManager.ts index c9924ce383..6d80a45540 100644 --- a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasManager.ts +++ b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasManager.ts @@ -177,9 +177,6 @@ export class CanvasManager { this.background = new CanvasBackground(this); this.stage.add(this.background.konva.layer); - this.inpaintMask = new CanvasInpaintMask(this.stateApi.getInpaintMaskState(), this); - this.stage.add(this.inpaintMask.konva.layer); - this.layers = new Map(); this.regions = new Map(); this.controlAdapters = new Map(); @@ -222,6 +219,9 @@ export class CanvasManager { this.getSelectedEntity(), (a, b) => a?.state === b?.state && a?.adapter === b?.adapter ); + + this.inpaintMask = new CanvasInpaintMask(this.stateApi.getInpaintMaskState(), this); + this.stage.add(this.inpaintMask.konva.layer); } enableDebugging() { @@ -273,11 +273,6 @@ export class CanvasManager { await this.preview.progressPreview.render(this.stateApi.$lastProgressEvent.get()); } - async renderInpaintMask() { - const inpaintMaskState = this.stateApi.getInpaintMaskState(); - await this.inpaintMask.render(inpaintMaskState); - } - async renderControlAdapters() { const { entities } = this.stateApi.getControlAdaptersState(); @@ -372,9 +367,9 @@ export class CanvasManager { const selectedEntity = this.getSelectedEntity(); if (selectedEntity) { if (selectedEntity.state.type === 'regional_guidance') { - currentFill = { ...selectedEntity.state.fill, a: state.settings.maskOpacity }; + currentFill = { ...selectedEntity.state.fill, a: 1 }; } else if (selectedEntity.state.type === 'inpaint_mask') { - currentFill = { ...state.inpaintMask.fill, a: state.settings.maskOpacity }; + currentFill = { ...state.inpaintMask.fill, a: 1 }; } } return currentFill; @@ -394,7 +389,10 @@ export class CanvasManager { } const layer = this.getSelectedEntity(); // TODO(psyche): Support other entity types - assert(layer?.adapter instanceof CanvasLayer, 'No selected layer'); + assert( + layer && (layer.adapter instanceof CanvasLayer || layer.adapter instanceof CanvasInpaintMask), + 'No selected layer' + ); layer.adapter.transformer.startTransform(); this.isTransforming.publish(true); } @@ -472,13 +470,16 @@ export class CanvasManager { 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.log.debug('Rendering inpaint mask'); - await this.renderInpaintMask(); + await this.inpaintMask.update({ + state: state.inpaintMask, + toolState: state.tool, + isSelected: state.selectedEntityIdentifier?.id === state.inpaintMask.id, + }); } if ( @@ -670,9 +671,14 @@ export class CanvasManager { | CanvasTransformer | CanvasObjectRenderer | CanvasLayer + | CanvasInpaintMask | CanvasStagingArea ): GetLoggingContext => { - if (instance instanceof CanvasLayer || instance instanceof CanvasStagingArea) { + if ( + instance instanceof CanvasLayer || + instance instanceof CanvasStagingArea || + instance instanceof CanvasInpaintMask + ) { return (extra?: JSONObject): JSONObject => { return { ...instance.manager.getLoggingContext(), diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasObjectRenderer.ts b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasObjectRenderer.ts index 7e6f7ac34a..35ae6209d5 100644 --- a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasObjectRenderer.ts +++ b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasObjectRenderer.ts @@ -1,8 +1,10 @@ import type { JSONObject } from 'common/types'; +import { rgbColorToString } from 'common/util/colorCodeTransformers'; import { deepClone } from 'common/util/deepClone'; import { CanvasBrushLineRenderer } from 'features/controlLayers/konva/CanvasBrushLine'; import { CanvasEraserLineRenderer } from 'features/controlLayers/konva/CanvasEraserLine'; import { CanvasImageRenderer } from 'features/controlLayers/konva/CanvasImage'; +import type { CanvasInpaintMask } from 'features/controlLayers/konva/CanvasInpaintMask'; import type { CanvasLayer } from 'features/controlLayers/konva/CanvasLayer'; import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager'; import { CanvasRectRenderer } from 'features/controlLayers/konva/CanvasRect'; @@ -13,6 +15,7 @@ import type { CanvasImageState, CanvasRectState, } from 'features/controlLayers/store/types'; +import Konva from 'konva'; import type { Logger } from 'roarr'; import { assert } from 'tsafe'; @@ -30,9 +33,10 @@ type AnyObjectState = CanvasBrushLineState | CanvasEraserLineState | CanvasImage */ export class CanvasObjectRenderer { static TYPE = 'object_renderer'; + static KONVA_COMPOSITING_RECT_NAME = 'compositing-rect'; id: string; - parent: CanvasLayer; + parent: CanvasLayer | CanvasInpaintMask; manager: CanvasManager; log: Logger; getLoggingContext: (extra?: JSONObject) => JSONObject; @@ -54,7 +58,11 @@ export class CanvasObjectRenderer { */ renderers: Map = new Map(); - constructor(parent: CanvasLayer) { + konva: { + compositingRect: Konva.Rect | null; + }; + + constructor(parent: CanvasLayer | CanvasInpaintMask, withCompositingRect: boolean = false) { this.id = getPrefixedId(CanvasObjectRenderer.TYPE); this.parent = parent; this.manager = parent.manager; @@ -62,6 +70,18 @@ export class CanvasObjectRenderer { this.log = this.manager.buildLogger(this.getLoggingContext); this.log.trace('Creating object renderer'); + this.konva = { + compositingRect: null, + }; + + if (withCompositingRect) { + this.konva.compositingRect = new Konva.Rect({ + name: CanvasObjectRenderer.KONVA_COMPOSITING_RECT_NAME, + listening: false, + }); + this.parent.konva.objectGroup.add(this.konva.compositingRect); + } + this.subscriptions.add( this.manager.toolState.subscribe((newVal, oldVal) => { if (newVal.selected !== oldVal.selected) { @@ -69,6 +89,21 @@ export class CanvasObjectRenderer { } }) ); + + // 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( + this.manager.stateApi.$stageAttrs.listen((attrs) => { + if (this.konva.compositingRect) { + this.konva.compositingRect.setAttrs({ + x: -attrs.position.x / attrs.scale, + y: -attrs.position.y / attrs.scale, + width: attrs.dimensions.width / attrs.scale, + height: attrs.dimensions.height / attrs.scale, + }); + } + }) + ); } /** @@ -96,6 +131,24 @@ export class CanvasObjectRenderer { didRender = (await this.renderObject(this.buffer)) || didRender; } + if (didRender && this.parent.type === 'inpaint_mask') { + assert(this.konva.compositingRect, 'Compositing rect must exist for inpaint mask'); + + // Convert the color to a string, stripping the alpha - the object group will handle opacity. + const rgbColor = rgbColorToString(this.parent.state.fill); + const maskOpacity = this.manager.stateApi.getMaskOpacity(); + + this.konva.compositingRect.setAttrs({ + fill: rgbColor, + opacity: maskOpacity, + // Draw this rect only where there are non-transparent pixels under it (e.g. the mask shapes) + globalCompositeOperation: 'source-in', + visible: true, + // This rect must always be on top of all other shapes + // zIndex: this.renderers.size + 1, + }); + } + return didRender; }; @@ -177,6 +230,12 @@ export class CanvasObjectRenderer { this.buffer = objectState; return await this.renderObject(this.buffer, true); + + // const didDraw = await this.renderObject(this.buffer, true); + // if (didDraw && this.konva.compositingRect) { + // this.konva.compositingRect.zIndex(this.renderers.size + 1); + // } + // return didDraw; }; /** @@ -204,11 +263,11 @@ export class CanvasObjectRenderer { this.buffer.id = getPrefixedId(this.buffer.type); if (this.buffer.type === 'brush_line') { - this.manager.stateApi.addBrushLine({ id: this.parent.id, brushLine: this.buffer }, 'layer'); + this.manager.stateApi.addBrushLine({ id: this.parent.id, brushLine: this.buffer }, this.parent.type); } else if (this.buffer.type === 'eraser_line') { - this.manager.stateApi.addEraserLine({ id: this.parent.id, eraserLine: this.buffer }, 'layer'); + this.manager.stateApi.addEraserLine({ id: this.parent.id, eraserLine: this.buffer }, this.parent.type); } else if (this.buffer.type === 'rect') { - this.manager.stateApi.addRect({ id: this.parent.id, rect: this.buffer }, 'layer'); + this.manager.stateApi.addRect({ id: this.parent.id, rect: this.buffer }, this.parent.type); } else { this.log.warn({ buffer: this.buffer }, 'Invalid buffer object type'); } diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasTransformer.ts b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasTransformer.ts index bc89f06995..ad99a5d7ce 100644 --- a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasTransformer.ts +++ b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasTransformer.ts @@ -1,3 +1,4 @@ +import type { CanvasInpaintMask } from 'features/controlLayers/konva/CanvasInpaintMask'; import type { CanvasLayer } from 'features/controlLayers/konva/CanvasLayer'; import { CanvasManager } from 'features/controlLayers/konva/CanvasManager'; import { getEmptyRect, getPrefixedId } from 'features/controlLayers/konva/util'; @@ -31,7 +32,7 @@ export class CanvasTransformer { static ANCHOR_HIT_PADDING = 10; id: string; - parent: CanvasLayer; + parent: CanvasLayer | CanvasInpaintMask; manager: CanvasManager; log: Logger; getLoggingContext: GetLoggingContext; @@ -89,7 +90,7 @@ export class CanvasTransformer { bboxOutline: Konva.Rect; }; - constructor(parent: CanvasLayer) { + constructor(parent: CanvasLayer | CanvasInpaintMask) { this.id = getPrefixedId(CanvasTransformer.TYPE); this.parent = parent; this.manager = parent.manager; @@ -354,7 +355,7 @@ export class CanvasTransformer { }; this.log.trace({ position }, 'Position changed'); - this.manager.stateApi.setEntityPosition({ id: this.parent.id, position }, 'layer'); + this.manager.stateApi.setEntityPosition({ id: this.parent.id, position }, this.parent.type); }); this.subscriptions.add( diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/events.ts b/invokeai/frontend/web/src/features/controlLayers/konva/events.ts index 992e4cbbf5..75b8ac9b17 100644 --- a/invokeai/frontend/web/src/features/controlLayers/konva/events.ts +++ b/invokeai/frontend/web/src/features/controlLayers/konva/events.ts @@ -128,8 +128,8 @@ export const setStageEventHandlers = (manager: CanvasManager): (() => void) => { $spaceKey, getBbox, getSettings, - setBrushWidth: onBrushWidthChanged, - setEraserWidth: onEraserWidthChanged, + setBrushWidth, + setEraserWidth, } = stateApi; function getIsPrimaryMouseDown(e: KonvaEventObject) { @@ -461,9 +461,9 @@ export const setStageEventHandlers = (manager: CanvasManager): (() => void) => { } // Holding ctrl or meta while scrolling changes the brush size if (toolState.selected === 'brush') { - onBrushWidthChanged(calculateNewBrushSize(toolState.brush.width, delta)); + setBrushWidth(calculateNewBrushSize(toolState.brush.width, delta)); } else if (toolState.selected === 'eraser') { - onEraserWidthChanged(calculateNewBrushSize(toolState.eraser.width, delta)); + setEraserWidth(calculateNewBrushSize(toolState.eraser.width, delta)); } } else { // We need the absolute cursor position - not the scaled position