diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasStageModule.ts b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasStageModule.ts index 9015017c22..ec9dd69faf 100644 --- a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasStageModule.ts +++ b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasStageModule.ts @@ -154,36 +154,36 @@ export class CanvasStageModule extends CanvasModuleBase { // If the stage _had_ no size just before this function was called, that means we've just mounted the stage or // maybe un-hidden it. In that case, the user is about to see the stage for the first time, so we should fit the // layers to the stage. If we don't do this, the layers will not be centered. - const shouldFitLayersAfterFittingStage = this.konva.stage.width() === 0 || this.konva.stage.height() === 0; + if (this.konva.stage.width() === 0 || this.konva.stage.height() === 0) { + // This fit must happen before the stage size is set, else we can end up with a brief flash of an incorrectly + // sized and scaled stage. + this.fitLayersToStage({ animate: false, targetWidth: containerWidth, targetHeight: containerHeight }); + } this.konva.stage.width(containerWidth); this.konva.stage.height(containerHeight); this.syncStageAttrs(); - - if (shouldFitLayersAfterFittingStage) { - this.fitLayersToStage(); - } }; /** * Fits the bbox to the stage. This will center the bbox and scale it to fit the stage with some padding. */ - fitBboxToStage = (): void => { + fitBboxToStage = (options?: { animate?: boolean; targetWidth?: number; targetHeight?: number }): void => { const { rect } = this.manager.stateApi.getBbox(); this.log.trace({ rect }, 'Fitting bbox to stage'); - this.fitRect(rect); + this.fitRect(rect, options); }; /** * Fits the visible canvas to the stage. This will center the canvas and scale it to fit the stage with some padding. */ - fitLayersToStage = (): void => { + fitLayersToStage = (options?: { animate?: boolean; targetWidth?: number; targetHeight?: number }): void => { const rect = this.manager.compositor.getVisibleRectOfType(); if (rect.width === 0 || rect.height === 0) { - this.fitBboxToStage(); + this.fitBboxToStage(options); } else { this.log.trace({ rect }, 'Fitting layers to stage'); - this.fitRect(rect); + this.fitRect(rect, options); } }; @@ -191,12 +191,12 @@ export class CanvasStageModule extends CanvasModuleBase { * Fits the bbox and layers to the stage. The union of the bbox and the visible layers will be centered and scaled * to fit the stage with some padding. */ - fitBboxAndLayersToStage = (): void => { + fitBboxAndLayersToStage = (options?: { animate?: boolean; targetWidth?: number; targetHeight?: number }): void => { const layersRect = this.manager.compositor.getVisibleRectOfType(); const bboxRect = this.manager.stateApi.getBbox().rect; const unionRect = getRectUnion(layersRect, bboxRect); this.log.trace({ bboxRect, layersRect, unionRect }, 'Fitting bbox and layers to stage'); - this.fitRect(unionRect); + this.fitRect(unionRect, options); }; /** @@ -204,16 +204,22 @@ export class CanvasStageModule extends CanvasModuleBase { * * The max scale is 1, but the stage can be scaled down to fit the rect. */ - fitRect = (rect: Rect): void => { - const { width, height } = this.getSize(); + fitRect = (rect: Rect, options?: { animate?: boolean; targetWidth?: number; targetHeight?: number }): void => { + const size = this.getSize(); + const { animate, targetWidth, targetHeight } = { + animate: true, + targetWidth: size.width, + targetHeight: size.height, + ...options, + }; // If the stage has no size, we can't fit anything to it - if (width === 0 || height === 0) { + if (targetWidth === 0 || targetHeight === 0) { return; } - const availableWidth = width - this.config.FIT_LAYERS_TO_STAGE_PADDING_PX * 2; - const availableHeight = height - this.config.FIT_LAYERS_TO_STAGE_PADDING_PX * 2; + const availableWidth = targetWidth - this.config.FIT_LAYERS_TO_STAGE_PADDING_PX * 2; + const availableHeight = targetHeight - this.config.FIT_LAYERS_TO_STAGE_PADDING_PX * 2; // Make sure we don't accidentally set the scale to something nonsensical, like a negative number, 0 or something // outside the valid range @@ -231,23 +237,33 @@ export class CanvasStageModule extends CanvasModuleBase { this._intendedScale = scale; this._activeSnapPoint = null; - const tween = new Konva.Tween({ - node: this.konva.stage, - duration: 0.15, - x, - y, - scaleX: scale, - scaleY: scale, - easing: Konva.Easings.EaseInOut, - onUpdate: () => { - this.syncStageAttrs(); - }, - onFinish: () => { - this.syncStageAttrs(); - tween.destroy(); - }, - }); - tween.play(); + if (animate) { + const tween = new Konva.Tween({ + node: this.konva.stage, + duration: 0.15, + x, + y, + scaleX: scale, + scaleY: scale, + easing: Konva.Easings.EaseInOut, + onUpdate: () => { + this.syncStageAttrs(); + }, + onFinish: () => { + this.syncStageAttrs(); + tween.destroy(); + }, + }); + tween.play(); + } else { + this.konva.stage.setAttrs({ + x, + y, + scaleX: scale, + scaleY: scale, + }); + this.syncStageAttrs(); + } }; /**