From 1ddea87c35452378dcc384dc5e142002457f9010 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Tue, 30 Jul 2024 21:10:45 +1000 Subject: [PATCH] feat(ui): trying to fix flicker after transform --- .../controlLayers/konva/CanvasLayer.ts | 47 ++++++++++++++----- .../controlLayers/konva/CanvasManager.ts | 2 +- .../controlLayers/store/canvasV2Slice.ts | 1 + .../controlLayers/store/layersReducers.ts | 16 +++++-- 4 files changed, 51 insertions(+), 15 deletions(-) diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasLayer.ts b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasLayer.ts index 670fa7a1b8..ef56026251 100644 --- a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasLayer.ts +++ b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasLayer.ts @@ -7,15 +7,16 @@ import { CanvasManager } from 'features/controlLayers/konva/CanvasManager'; import { CanvasRect } from 'features/controlLayers/konva/CanvasRect'; import { getBrushLineId, getEraserLineId, getRectShapeId } from 'features/controlLayers/konva/naming'; import { konvaNodeToBlob, mapId, previewBlob } from 'features/controlLayers/konva/util'; -import { layerRasterized } from 'features/controlLayers/store/canvasV2Slice'; -import type { - BrushLine, - CanvasV2State, - Coordinate, - EraserLine, - LayerEntity, - Rect, - RectShape, +import { layerAllObjectsDeletedExceptOne, layerRasterized } from 'features/controlLayers/store/canvasV2Slice'; +import { + type BrushLine, + type CanvasV2State, + type Coordinate, + type EraserLine, + imageDTOToImageObject, + type LayerEntity, + type Rect, + type RectShape, } from 'features/controlLayers/store/types'; import Konva from 'konva'; import { debounce, get } from 'lodash-es'; @@ -53,6 +54,8 @@ export class CanvasLayer { _isFirstRender: boolean; isTransforming: boolean; + isPendingBboxCalculation: boolean; + rasterizedObjectId: string | null; rect: Rect; bbox: Rect; @@ -188,6 +191,8 @@ export class CanvasLayer { this._bboxNeedsUpdate = true; this.isTransforming = false; this._isFirstRender = true; + this.rasterizedObjectId = null; + this.isPendingBboxCalculation = false; this._log = this.manager.getLogger(`layer_${this.id}`); } @@ -326,6 +331,12 @@ export class CanvasLayer { if (didUpdate) { this.calculateBbox(); } + + if (this.isTransforming && this.rasterizedObjectId) { + this.manager._store.dispatch(layerAllObjectsDeletedExceptOne({ id: this.id, objectId: this.rasterizedObjectId })); + this.isTransforming = false; + this.rasterizedObjectId = null; + } } async updateOpacity(arg?: { opacity: number }) { @@ -388,6 +399,10 @@ export class CanvasLayer { async updateBbox() { this._log.trace('Updating bbox'); + if (this.isPendingBboxCalculation) { + return; + } + // 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. if (this.bbox.width === 0 || this.bbox.height === 0) { @@ -547,11 +562,16 @@ export class CanvasLayer { } 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 } })); - this.isTransforming = false; + const imageObject = imageDTOToImageObject(this.id, uuidv4(), imageDTO); + dispatch(layerRasterized({ id: this.id, imageObject, position: { x: rect.x, y: rect.y } })); + this.rasterizedObjectId = imageObject.id; this.resetScale(); } + async finalizeTransform() { + // + } + async cancelTransform() { this._log.debug('Canceling transform'); @@ -572,9 +592,12 @@ export class CanvasLayer { calculateBbox = debounce(() => { this._log.debug('Calculating bbox'); + this.isPendingBboxCalculation = true; + if (this.objects.size === 0) { this.rect = this.getDefaultRect(); this.bbox = this.getDefaultRect(); + this.isPendingBboxCalculation = false; this.updateBbox(); return; } @@ -607,6 +630,7 @@ export class CanvasLayer { if (!needsPixelBbox) { this.rect = deepClone(rect); this.bbox = deepClone(rect); + this.isPendingBboxCalculation = false; this._log.trace({ bbox: this.bbox, rect: this.rect }, 'Got bbox from client rect'); this.updateBbox(); return; @@ -636,6 +660,7 @@ export class CanvasLayer { } else { this.bbox = deepClone(rect); } + this.isPendingBboxCalculation = false; this._log.trace({ bbox: this.bbox, rect: this.rect, extents }, `Got bbox from worker`); this.updateBbox(); clone.destroy(); diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasManager.ts b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasManager.ts index d1e0fbee6b..f028c6a243 100644 --- a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasManager.ts +++ b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasManager.ts @@ -329,7 +329,7 @@ export class CanvasManager { for (const canvasLayer of this.layers.values()) { if (!state.layers.entities.find((l) => l.id === canvasLayer.id)) { this.log.debug(`Destroying deleted layer ${canvasLayer.id}`); - canvasLayer.destroy(); + await canvasLayer.destroy(); this.layers.delete(canvasLayer.id); } } diff --git a/invokeai/frontend/web/src/features/controlLayers/store/canvasV2Slice.ts b/invokeai/frontend/web/src/features/controlLayers/store/canvasV2Slice.ts index b302a00ba7..90d0f7f933 100644 --- a/invokeai/frontend/web/src/features/controlLayers/store/canvasV2Slice.ts +++ b/invokeai/frontend/web/src/features/controlLayers/store/canvasV2Slice.ts @@ -220,6 +220,7 @@ export const { layerTranslated, layerBboxChanged, layerImageAdded, + layerAllObjectsDeletedExceptOne, layerAllDeleted, layerImageCacheChanged, layerScaled, diff --git a/invokeai/frontend/web/src/features/controlLayers/store/layersReducers.ts b/invokeai/frontend/web/src/features/controlLayers/store/layersReducers.ts index 5d538bd821..ae2eaf3e1a 100644 --- a/invokeai/frontend/web/src/features/controlLayers/store/layersReducers.ts +++ b/invokeai/frontend/web/src/features/controlLayers/store/layersReducers.ts @@ -10,6 +10,7 @@ import type { CanvasV2State, Coordinate, EraserLine, + ImageObject, ImageObjectAddedArg, LayerEntity, PositionChangedArg, @@ -252,16 +253,25 @@ export const layersReducers = { const { imageDTO } = action.payload; state.layers.imageCache = imageDTO ? imageDTOToImageWithDims(imageDTO) : null; }, - layerRasterized: (state, action: PayloadAction<{ id: string; imageDTO: ImageDTO; position: Coordinate }>) => { - const { id, imageDTO, position } = action.payload; + layerRasterized: (state, action: PayloadAction<{ id: string; imageObject: ImageObject; position: Coordinate }>) => { + const { id, imageObject, position } = action.payload; const layer = selectLayer(state, id); if (!layer) { return; } - layer.objects = [imageDTOToImageObject(id, uuidv4(), imageDTO)]; + layer.objects.push(imageObject); layer.position = position; state.layers.imageCache = null; }, + layerAllObjectsDeletedExceptOne: (state, action: PayloadAction<{ id: string; objectId: string }>) => { + const { id, objectId } = action.payload; + const layer = selectLayer(state, id); + if (!layer) { + return; + } + layer.objects = layer.objects.filter((obj) => obj.id === objectId); + state.layers.imageCache = null; + }, } satisfies SliceCaseReducers; const scalePoints = (points: number[], scaleX: number, scaleY: number) => {