diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/renderers/bbox.ts b/invokeai/frontend/web/src/features/controlLayers/konva/renderers/bbox.ts index bc4c42fbd2..5b3df88aea 100644 --- a/invokeai/frontend/web/src/features/controlLayers/konva/renderers/bbox.ts +++ b/invokeai/frontend/web/src/features/controlLayers/konva/renderers/bbox.ts @@ -1,13 +1,13 @@ import openBase64ImageInTab from 'common/util/openBase64ImageInTab'; import { imageDataToDataURL } from 'features/canvas/util/blobToDataURL'; import { + CA_LAYER_IMAGE_NAME, LAYER_BBOX_NAME, RASTER_LAYER_OBJECT_GROUP_NAME, RG_LAYER_OBJECT_GROUP_NAME, } from 'features/controlLayers/konva/naming'; import { createBboxRect } from 'features/controlLayers/konva/renderers/objects'; -import type { LayerData } from 'features/controlLayers/store/types'; -import { isRegionalGuidanceLayer } from 'features/controlLayers/store/types'; +import type { ControlAdapterData, LayerData, RegionalGuidanceData } from 'features/controlLayers/store/types'; import Konva from 'konva'; import type { IRect } from 'konva/lib/types'; import { assert } from 'tsafe'; @@ -175,37 +175,52 @@ export const getLayerBboxFast = (layer: Konva.Layer): IRect => { }; const filterRGChildren = (node: Konva.Node): boolean => node.name() === RG_LAYER_OBJECT_GROUP_NAME; -const filterRasterChildren = (node: Konva.Node): boolean => node.name() === RASTER_LAYER_OBJECT_GROUP_NAME; +const filterLayerChildren = (node: Konva.Node): boolean => node.name() === RASTER_LAYER_OBJECT_GROUP_NAME; +const filterCAChildren = (node: Konva.Node): boolean => node.name() === CA_LAYER_IMAGE_NAME; /** * Calculates the bbox of each regional guidance layer. Only calculates if the mask has changed. * @param stage The konva stage - * @param layerStates An array of layers to calculate bboxes for + * @param entityStates An array of layers to calculate bboxes for * @param onBboxChanged Callback for when the bounding box changes */ export const updateBboxes = ( stage: Konva.Stage, - layerStates: LayerData[], + entityStates: (ControlAdapterData | LayerData | RegionalGuidanceData)[], onBboxChanged: (layerId: string, bbox: IRect | null) => void ): void => { - for (const layerState of layerStates) { - const konvaLayer = stage.findOne(`#${layerState.id}`); - assert(konvaLayer, `Layer ${layerState.id} not found in stage`); + for (const entityState of entityStates) { + const konvaLayer = stage.findOne(`#${entityState.id}`); + assert(konvaLayer, `Layer ${entityState.id} not found in stage`); // We only need to recalculate the bbox if the layer has changed - if (layerState.bboxNeedsUpdate) { - const bboxRect = konvaLayer.findOne(`.${LAYER_BBOX_NAME}`) ?? createBboxRect(layerState, konvaLayer); + if (entityState.bboxNeedsUpdate) { + const bboxRect = konvaLayer.findOne(`.${LAYER_BBOX_NAME}`) ?? createBboxRect(entityState, konvaLayer); // Hide the bbox while we calculate the new bbox, else the bbox will be included in the calculation const visible = bboxRect.visible(); bboxRect.visible(false); - if (layerState.objects.length === 0) { - // No objects - no bbox to calculate - onBboxChanged(layerState.id, null); - } else { - // Calculate the bbox by rendering the layer and checking its pixels - const filterChildren = isRegionalGuidanceLayer(layerState) ? filterRGChildren : filterRasterChildren; - onBboxChanged(layerState.id, getLayerBboxPixels(konvaLayer, filterChildren)); + if (entityState.type === 'layer') { + if (entityState.objects.length === 0) { + // No objects - no bbox to calculate + onBboxChanged(entityState.id, null); + } else { + onBboxChanged(entityState.id, getLayerBboxPixels(konvaLayer, filterLayerChildren)); + } + } else if (entityState.type === 'control_adapter') { + if (!entityState.image && !entityState.processedImage) { + // No objects - no bbox to calculate + onBboxChanged(entityState.id, null); + } else { + onBboxChanged(entityState.id, getLayerBboxPixels(konvaLayer, filterCAChildren)); + } + } else if (entityState.type === 'regional_guidance') { + if (entityState.objects.length === 0) { + // No objects - no bbox to calculate + onBboxChanged(entityState.id, null); + } else { + onBboxChanged(entityState.id, getLayerBboxPixels(konvaLayer, filterRGChildren)); + } } // Restore the visibility of the bbox diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/renderers/iiLayer.ts b/invokeai/frontend/web/src/features/controlLayers/konva/renderers/iiLayer.ts deleted file mode 100644 index a638f69f39..0000000000 --- a/invokeai/frontend/web/src/features/controlLayers/konva/renderers/iiLayer.ts +++ /dev/null @@ -1,155 +0,0 @@ -import { - getCALayerImageId, - getIILayerImageId, - INITIAL_IMAGE_LAYER_IMAGE_NAME, - INITIAL_IMAGE_LAYER_NAME, -} from 'features/controlLayers/konva/naming'; -import type { InitialImageLayer } from 'features/controlLayers/store/types'; -import Konva from 'konva'; -import type { ImageDTO } from 'services/api/types'; - -/** - * Logic for creating and rendering initial image layers. Well, just the one, actually, because it's a singleton. - * TODO(psyche): Raster layers effectively supersede the initial image layer type. - */ - -/** - * Creates an initial image konva layer. - * @param stage The konva stage - * @param layerState The initial image layer state - */ -const createIILayer = (stage: Konva.Stage, layerState: InitialImageLayer): Konva.Layer => { - const konvaLayer = new Konva.Layer({ - id: layerState.id, - name: INITIAL_IMAGE_LAYER_NAME, - imageSmoothingEnabled: true, - listening: false, - }); - stage.add(konvaLayer); - return konvaLayer; -}; - -/** - * Creates the konva image for an initial image layer. - * @param konvaLayer The konva layer - * @param imageEl The image element - */ -const createIILayerImage = (konvaLayer: Konva.Layer, imageEl: HTMLImageElement): Konva.Image => { - const konvaImage = new Konva.Image({ - name: INITIAL_IMAGE_LAYER_IMAGE_NAME, - image: imageEl, - }); - konvaLayer.add(konvaImage); - return konvaImage; -}; - -/** - * Updates an initial image layer's attributes (width, height, opacity, visibility). - * @param stage The konva stage - * @param konvaImage The konva image - * @param layerState The initial image layer state - */ -const updateIILayerImageAttrs = (stage: Konva.Stage, konvaImage: Konva.Image, layerState: InitialImageLayer): void => { - // Konva erroneously reports NaN for width and height when the stage is hidden. This causes errors when caching, - // but it doesn't seem to break anything. - // TODO(psyche): Investigate and report upstream. - const newWidth = stage.width() / stage.scaleX(); - const newHeight = stage.height() / stage.scaleY(); - if ( - konvaImage.width() !== newWidth || - konvaImage.height() !== newHeight || - konvaImage.visible() !== layerState.isEnabled - ) { - konvaImage.setAttrs({ - opacity: layerState.opacity, - scaleX: 1, - scaleY: 1, - width: stage.width() / stage.scaleX(), - height: stage.height() / stage.scaleY(), - visible: layerState.isEnabled, - }); - } - if (konvaImage.opacity() !== layerState.opacity) { - konvaImage.opacity(layerState.opacity); - } -}; - -/** - * Update an initial image layer's image source when the image changes. - * @param stage The konva stage - * @param konvaLayer The konva layer - * @param layerState The initial image layer state - * @param getImageDTO A function to retrieve an image DTO from the server, used to update the image source - */ -const updateIILayerImageSource = async ( - stage: Konva.Stage, - konvaLayer: Konva.Layer, - layerState: InitialImageLayer, - getImageDTO: (imageName: string) => Promise -): Promise => { - if (layerState.image) { - const imageName = layerState.image.name; - const imageDTO = await getImageDTO(imageName); - if (!imageDTO) { - return; - } - const imageEl = new Image(); - const imageId = getIILayerImageId(layerState.id, imageName); - imageEl.onload = () => { - // Find the existing image or create a new one - must find using the name, bc the id may have just changed - const konvaImage = - konvaLayer.findOne(`.${INITIAL_IMAGE_LAYER_IMAGE_NAME}`) ?? - createIILayerImage(konvaLayer, imageEl); - - // Update the image's attributes - konvaImage.setAttrs({ - id: imageId, - image: imageEl, - }); - updateIILayerImageAttrs(stage, konvaImage, layerState); - imageEl.id = imageId; - }; - imageEl.src = imageDTO.image_url; - } else { - konvaLayer.findOne(`.${INITIAL_IMAGE_LAYER_IMAGE_NAME}`)?.destroy(); - } -}; - -/** - * Renders an initial image layer. - * @param stage The konva stage - * @param layerState The initial image layer state - * @param getImageDTO A function to retrieve an image DTO from the server, used to update the image source - */ -export const renderIILayer = ( - stage: Konva.Stage, - layerState: InitialImageLayer, - zIndex: number, - getImageDTO: (imageName: string) => Promise -): void => { - const konvaLayer = stage.findOne(`#${layerState.id}`) ?? createIILayer(stage, layerState); - - konvaLayer.zIndex(zIndex); - - const konvaImage = konvaLayer.findOne(`.${INITIAL_IMAGE_LAYER_IMAGE_NAME}`); - const canvasImageSource = konvaImage?.image(); - - let imageSourceNeedsUpdate = false; - - if (canvasImageSource instanceof HTMLImageElement) { - const image = layerState.image; - if (image && canvasImageSource.id !== getCALayerImageId(layerState.id, image.name)) { - imageSourceNeedsUpdate = true; - } else if (!image) { - imageSourceNeedsUpdate = true; - } - } else if (!canvasImageSource) { - imageSourceNeedsUpdate = true; - } - - if (imageSourceNeedsUpdate) { - updateIILayerImageSource(stage, konvaLayer, layerState, getImageDTO); - } else if (konvaImage) { - updateIILayerImageAttrs(stage, konvaImage, layerState); - } -};