diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasInpaintMask.ts b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasInpaintMask.ts index c510e3228e..0f14cc4fd9 100644 --- a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasInpaintMask.ts +++ b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasInpaintMask.ts @@ -6,7 +6,7 @@ import { CanvasRect } from 'features/controlLayers/konva/CanvasRect'; import { getNodeBboxFast } from 'features/controlLayers/konva/entityBbox'; import { getObjectGroupId, INPAINT_MASK_LAYER_ID } from 'features/controlLayers/konva/naming'; import { mapId } from 'features/controlLayers/konva/util'; -import type { BrushLine, EraserLine, InpaintMaskEntity } from 'features/controlLayers/store/types'; +import type { BrushLine, EraserLine, InpaintMaskEntity, RectShape } from 'features/controlLayers/store/types'; import { isDrawingTool, RGBA_RED } from 'features/controlLayers/store/types'; import Konva from 'konva'; import { assert } from 'tsafe'; @@ -21,7 +21,7 @@ export class CanvasInpaintMask { compositingRect: Konva.Rect; transformer: Konva.Transformer; objects: Map; - private drawingBuffer: BrushLine | EraserLine | null; + private drawingBuffer: BrushLine | EraserLine | RectShape | null; private inpaintMaskState: InpaintMaskEntity; constructor(entity: InpaintMaskEntity, manager: CanvasManager) { @@ -71,7 +71,7 @@ export class CanvasInpaintMask { return this.drawingBuffer; } - async setDrawingBuffer(obj: BrushLine | EraserLine | null) { + async setDrawingBuffer(obj: BrushLine | EraserLine | RectShape | null) { this.drawingBuffer = obj; if (this.drawingBuffer) { if (this.drawingBuffer.type === 'brush_line') { @@ -91,6 +91,8 @@ export class CanvasInpaintMask { this.manager.stateApi.onBrushLineAdded2({ id: this.id, brushLine: this.drawingBuffer }, 'inpaint_mask'); } else if (this.drawingBuffer.type === 'eraser_line') { this.manager.stateApi.onEraserLineAdded2({ id: this.id, eraserLine: this.drawingBuffer }, 'inpaint_mask'); + } else if (this.drawingBuffer.type === 'rect_shape') { + this.manager.stateApi.onRectShapeAdded2({ id: this.id, rectShape: this.drawingBuffer }, 'layer'); } this.setDrawingBuffer(null); } diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasLayer.ts b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasLayer.ts index fc074ddc40..92be18b40c 100644 --- a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasLayer.ts +++ b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasLayer.ts @@ -5,7 +5,7 @@ import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager'; import { CanvasRect } from 'features/controlLayers/konva/CanvasRect'; import { getObjectGroupId } from 'features/controlLayers/konva/naming'; import { mapId } from 'features/controlLayers/konva/util'; -import type { BrushLine, EraserLine, LayerEntity } from 'features/controlLayers/store/types'; +import type { BrushLine, EraserLine, LayerEntity, RectShape } from 'features/controlLayers/store/types'; import { isDrawingTool } from 'features/controlLayers/store/types'; import Konva from 'konva'; import { assert } from 'tsafe'; @@ -19,7 +19,7 @@ export class CanvasLayer { objectsGroup: Konva.Group; transformer: Konva.Transformer; objects: Map; - private drawingBuffer: BrushLine | EraserLine | null; + private drawingBuffer: BrushLine | EraserLine | RectShape | null; private layerState: LayerEntity; constructor(entity: LayerEntity, manager: CanvasManager) { @@ -70,7 +70,7 @@ export class CanvasLayer { return this.drawingBuffer; } - async setDrawingBuffer(obj: BrushLine | EraserLine | null) { + async setDrawingBuffer(obj: BrushLine | EraserLine | RectShape | null) { if (obj) { this.drawingBuffer = obj; await this.renderObject(this.drawingBuffer, true); @@ -88,6 +88,8 @@ export class CanvasLayer { this.manager.stateApi.onBrushLineAdded2({ id: this.id, brushLine: this.drawingBuffer }, 'layer'); } else if (this.drawingBuffer.type === 'eraser_line') { this.manager.stateApi.onEraserLineAdded2({ id: this.id, eraserLine: this.drawingBuffer }, 'layer'); + } else if (this.drawingBuffer.type === 'rect_shape') { + this.manager.stateApi.onRectShapeAdded2({ id: this.id, rectShape: this.drawingBuffer }, 'layer'); } this.setDrawingBuffer(null); } diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasRegion.ts b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasRegion.ts index 2e735e4b5b..90111cfa22 100644 --- a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasRegion.ts +++ b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasRegion.ts @@ -6,7 +6,7 @@ import { CanvasRect } from 'features/controlLayers/konva/CanvasRect'; import { getNodeBboxFast } from 'features/controlLayers/konva/entityBbox'; import { getObjectGroupId } from 'features/controlLayers/konva/naming'; import { mapId } from 'features/controlLayers/konva/util'; -import type { BrushLine, EraserLine, RegionEntity } from 'features/controlLayers/store/types'; +import type { BrushLine, EraserLine, RectShape, RegionEntity } from 'features/controlLayers/store/types'; import { isDrawingTool, RGBA_RED } from 'features/controlLayers/store/types'; import Konva from 'konva'; import { assert } from 'tsafe'; @@ -21,7 +21,7 @@ export class CanvasRegion { compositingRect: Konva.Rect; transformer: Konva.Transformer; objects: Map; - private drawingBuffer: BrushLine | EraserLine | null; + private drawingBuffer: BrushLine | EraserLine | RectShape | null; private regionState: RegionEntity; constructor(entity: RegionEntity, manager: CanvasManager) { @@ -71,7 +71,7 @@ export class CanvasRegion { return this.drawingBuffer; } - async setDrawingBuffer(obj: BrushLine | EraserLine | null) { + async setDrawingBuffer(obj: BrushLine | EraserLine | RectShape | null) { this.drawingBuffer = obj; if (this.drawingBuffer) { if (this.drawingBuffer.type === 'brush_line') { @@ -90,6 +90,8 @@ export class CanvasRegion { this.manager.stateApi.onBrushLineAdded2({ id: this.id, brushLine: this.drawingBuffer }, 'regional_guidance'); } else if (this.drawingBuffer.type === 'eraser_line') { this.manager.stateApi.onEraserLineAdded2({ id: this.id, eraserLine: this.drawingBuffer }, 'regional_guidance'); + } else if (this.drawingBuffer.type === 'rect_shape') { + this.manager.stateApi.onRectShapeAdded2({ id: this.id, rectShape: this.drawingBuffer }, 'layer'); } this.setDrawingBuffer(null); } diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasStateApi.ts b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasStateApi.ts index 735fdb635e..f936cf1503 100644 --- a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasStateApi.ts +++ b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasStateApi.ts @@ -25,6 +25,7 @@ import { imImageCacheChanged, imLinePointAdded, imRectAdded, + imRectShapeAdded2, imScaled, imTranslated, layerBboxChanged, @@ -35,6 +36,7 @@ import { layerImageCacheChanged, layerLinePointAdded, layerRectAdded, + layerRectShapeAdded2, layerScaled, layerTranslated, rgBboxChanged, @@ -45,6 +47,7 @@ import { rgImageCacheChanged, rgLinePointAdded, rgRectAdded, + rgRectShapeAdded2, rgScaled, rgTranslated, toolBufferChanged, @@ -59,6 +62,7 @@ import type { EraserLineAddedArg, PointAddedToLineArg, PosChangedArg, + RectShape, RectShapeAddedArg, ScaleChangedArg, Tool, @@ -175,6 +179,16 @@ export class CanvasStateApi { this.store.dispatch(imRectAdded(arg)); } }; + onRectShapeAdded2 = (arg: { id: string; rectShape: RectShape }, entityType: CanvasEntity['type']) => { + log.debug('Rect shape added'); + if (entityType === 'layer') { + this.store.dispatch(layerRectShapeAdded2(arg)); + } else if (entityType === 'regional_guidance') { + this.store.dispatch(rgRectShapeAdded2(arg)); + } else if (entityType === 'inpaint_mask') { + this.store.dispatch(imRectShapeAdded2(arg)); + } + }; onBboxTransformed = (bbox: IRect) => { log.debug('Generation bbox transformed'); this.store.dispatch(bboxChanged(bbox)); diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasTool.ts b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasTool.ts index 825127a596..54a9501559 100644 --- a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasTool.ts +++ b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasTool.ts @@ -5,7 +5,6 @@ import { BRUSH_BORDER_OUTER_COLOR, BRUSH_ERASER_BORDER_WIDTH, } from 'features/controlLayers/konva/constants'; -import { PREVIEW_RECT_ID } from 'features/controlLayers/konva/naming'; import Konva from 'konva'; export class CanvasTool { @@ -23,10 +22,10 @@ export class CanvasTool { innerBorderCircle: Konva.Circle; outerBorderCircle: Konva.Circle; }; - rect: { - group: Konva.Group; - fillRect: Konva.Rect; - }; + // rect: { + // group: Konva.Group; + // fillRect: Konva.Rect; + // }; constructor(manager: CanvasManager) { this.manager = manager; @@ -83,17 +82,17 @@ export class CanvasTool { this.eraser.group.add(this.eraser.outerBorderCircle); this.group.add(this.eraser.group); - // Create the rect preview - this is a rectangle drawn from the last mouse down position to the current cursor position - this.rect = { - group: new Konva.Group(), - fillRect: new Konva.Rect({ - id: PREVIEW_RECT_ID, - listening: false, - strokeEnabled: false, - }), - }; - this.rect.group.add(this.rect.fillRect); - this.group.add(this.rect.group); + // // Create the rect preview - this is a rectangle drawn from the last mouse down position to the current cursor position + // this.rect = { + // group: new Konva.Group(), + // fillRect: new Konva.Rect({ + // id: PREVIEW_RECT_ID, + // listening: false, + // strokeEnabled: false, + // }), + // }; + // this.rect.group.add(this.rect.fillRect); + // this.group.add(this.rect.group); } scaleTool = () => { @@ -189,7 +188,7 @@ export class CanvasTool { this.brush.group.visible(true); this.eraser.group.visible(false); - this.rect.group.visible(false); + // this.rect.group.visible(false); } else if (cursorPos && tool === 'eraser') { const scale = stage.scaleX(); // Update the fill circle @@ -215,23 +214,23 @@ export class CanvasTool { this.brush.group.visible(false); this.eraser.group.visible(true); - this.rect.group.visible(false); - } else if (cursorPos && lastMouseDownPos && tool === 'rect') { - this.rect.fillRect.setAttrs({ - x: Math.min(cursorPos.x, lastMouseDownPos.x), - y: Math.min(cursorPos.y, lastMouseDownPos.y), - width: Math.abs(cursorPos.x - lastMouseDownPos.x), - height: Math.abs(cursorPos.y - lastMouseDownPos.y), - fill: rgbaColorToString(currentFill), - visible: true, - }); - this.brush.group.visible(false); - this.eraser.group.visible(false); - this.rect.group.visible(true); + // this.rect.group.visible(false); + // } else if (cursorPos && lastMouseDownPos && tool === 'rect') { + // this.rect.fillRect.setAttrs({ + // x: Math.min(cursorPos.x, lastMouseDownPos.x), + // y: Math.min(cursorPos.y, lastMouseDownPos.y), + // width: Math.abs(cursorPos.x - lastMouseDownPos.x), + // height: Math.abs(cursorPos.y - lastMouseDownPos.y), + // fill: rgbaColorToString(currentFill), + // visible: true, + // }); + // this.brush.group.visible(false); + // this.eraser.group.visible(false); + // this.rect.group.visible(true); } else { this.brush.group.visible(false); this.eraser.group.visible(false); - this.rect.group.visible(false); + // this.rect.group.visible(false); } } } diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/events.ts b/invokeai/frontend/web/src/features/controlLayers/konva/events.ts index 82ba286a8a..69ceb5a291 100644 --- a/invokeai/frontend/web/src/features/controlLayers/konva/events.ts +++ b/invokeai/frontend/web/src/features/controlLayers/konva/events.ts @@ -16,7 +16,7 @@ import { clamp } from 'lodash-es'; import { v4 as uuidv4 } from 'uuid'; import { BRUSH_SPACING_TARGET_SCALE, CANVAS_SCALE_BY, MAX_CANVAS_SCALE, MIN_CANVAS_SCALE } from './constants'; -import { getBrushLineId, getEraserLineId } from './naming'; +import { getBrushLineId, getEraserLineId, getRectShapeId } from './naming'; /** * Updates the last cursor position atom with the current cursor position, returning the new position or `null` if the @@ -267,6 +267,21 @@ export const setStageEventHandlers = (manager: CanvasManager): (() => void) => { } setLastAddedPoint(pos); } + + if (toolState.selected === 'rect') { + if (selectedEntityAdapter.getDrawingBuffer()) { + selectedEntityAdapter.finalizeDrawingBuffer(); + } + await selectedEntityAdapter.setDrawingBuffer({ + id: getRectShapeId(selectedEntityAdapter.id, uuidv4()), + type: 'rect_shape', + x: pos.x - selectedEntity.x, + y: pos.y - selectedEntity.y, + width: 0, + height: 0, + color: getCurrentFill(), + }); + } } manager.preview.tool.render(); }); @@ -284,7 +299,8 @@ export const setStageEventHandlers = (manager: CanvasManager): (() => void) => { isDrawableEntity(selectedEntity) && selectedEntityAdapter && isDrawableEntityAdapter(selectedEntityAdapter) && - !getSpaceKey() + !getSpaceKey() && + getIsPrimaryMouseDown(e) ) { const toolState = getToolState(); @@ -307,22 +323,28 @@ export const setStageEventHandlers = (manager: CanvasManager): (() => void) => { } if (toolState.selected === 'rect') { - const lastMouseDownPos = getLastMouseDownPos(); - if (lastMouseDownPos) { - onRectShapeAdded( - { - id: selectedEntity.id, - rect: { - x: Math.min(pos.x - selectedEntity.x, lastMouseDownPos.x - selectedEntity.x), - y: Math.min(pos.y - selectedEntity.y, lastMouseDownPos.y - selectedEntity.y), - width: Math.abs(pos.x - lastMouseDownPos.x), - height: Math.abs(pos.y - lastMouseDownPos.y), - }, - color: getCurrentFill(), - }, - selectedEntity.type - ); + const drawingBuffer = selectedEntityAdapter.getDrawingBuffer(); + if (drawingBuffer?.type === 'rect_shape') { + selectedEntityAdapter.finalizeDrawingBuffer(); + } else { + await selectedEntityAdapter.setDrawingBuffer(null); } + // const lastMouseDownPos = getLastMouseDownPos(); + // if (lastMouseDownPos) { + // onRectShapeAdded( + // { + // id: selectedEntity.id, + // rect: { + // x: Math.min(pos.x - selectedEntity.x, lastMouseDownPos.x - selectedEntity.x), + // y: Math.min(pos.y - selectedEntity.y, lastMouseDownPos.y - selectedEntity.y), + // width: Math.abs(pos.x - lastMouseDownPos.x), + // height: Math.abs(pos.y - lastMouseDownPos.y), + // }, + // color: getCurrentFill(), + // }, + // selectedEntity.type + // ); + // } } setLastMouseDownPos(null); @@ -415,6 +437,19 @@ export const setStageEventHandlers = (manager: CanvasManager): (() => void) => { setLastAddedPoint(pos); } } + + if (toolState.selected === 'rect') { + const drawingBuffer = selectedEntityAdapter.getDrawingBuffer(); + if (drawingBuffer) { + if (drawingBuffer.type === 'rect_shape') { + drawingBuffer.width = pos.x - selectedEntity.x - drawingBuffer.x; + drawingBuffer.height = pos.y - selectedEntity.y - drawingBuffer.y; + await selectedEntityAdapter.setDrawingBuffer(drawingBuffer); + } else { + await selectedEntityAdapter.setDrawingBuffer(null); + } + } + } } manager.preview.tool.render(); }); @@ -446,6 +481,11 @@ export const setStageEventHandlers = (manager: CanvasManager): (() => void) => { drawingBuffer.points.push(pos.x - selectedEntity.x, pos.y - selectedEntity.y); await selectedEntityAdapter.setDrawingBuffer(drawingBuffer); selectedEntityAdapter.finalizeDrawingBuffer(); + } else if (toolState.selected === 'rect' && drawingBuffer?.type === 'rect_shape') { + drawingBuffer.width = pos.x - selectedEntity.x - drawingBuffer.x; + drawingBuffer.height = pos.y - selectedEntity.y - drawingBuffer.y; + await selectedEntityAdapter.setDrawingBuffer(drawingBuffer); + selectedEntityAdapter.finalizeDrawingBuffer(); } } diff --git a/invokeai/frontend/web/src/features/controlLayers/store/canvasV2Slice.ts b/invokeai/frontend/web/src/features/controlLayers/store/canvasV2Slice.ts index 104a6d13b2..14107c60ff 100644 --- a/invokeai/frontend/web/src/features/controlLayers/store/canvasV2Slice.ts +++ b/invokeai/frontend/web/src/features/controlLayers/store/canvasV2Slice.ts @@ -349,10 +349,13 @@ export const { stagingAreaPreviousImageSelected, layerBrushLineAdded2, layerEraserLineAdded2, + layerRectShapeAdded2, rgBrushLineAdded2, rgEraserLineAdded2, + rgRectShapeAdded2, imBrushLineAdded2, imEraserLineAdded2, + imRectShapeAdded2, } = canvasV2Slice.actions; export const selectCanvasV2Slice = (state: RootState) => state.canvasV2; diff --git a/invokeai/frontend/web/src/features/controlLayers/store/inpaintMaskReducers.ts b/invokeai/frontend/web/src/features/controlLayers/store/inpaintMaskReducers.ts index 9dc78b6105..05e6bc4b22 100644 --- a/invokeai/frontend/web/src/features/controlLayers/store/inpaintMaskReducers.ts +++ b/invokeai/frontend/web/src/features/controlLayers/store/inpaintMaskReducers.ts @@ -5,6 +5,7 @@ import type { CanvasV2State, EraserLine, InpaintMaskEntity, + RectShape, ScaleChangedArg, } from 'features/controlLayers/store/types'; import { imageDTOToImageWithDims, RGBA_RED } from 'features/controlLayers/store/types'; @@ -99,6 +100,12 @@ export const inpaintMaskReducers = { state.inpaintMask.bboxNeedsUpdate = true; state.layers.imageCache = null; }, + imRectShapeAdded2: (state, action: PayloadAction<{ rectShape: RectShape }>) => { + const { rectShape } = action.payload; + state.inpaintMask.objects.push(rectShape); + state.inpaintMask.bboxNeedsUpdate = true; + state.layers.imageCache = null; + }, imEraserLineAdded: { reducer: (state, action: PayloadAction & { lineId: string }>) => { const { points, lineId, width, clip } = action.payload; diff --git a/invokeai/frontend/web/src/features/controlLayers/store/layersReducers.ts b/invokeai/frontend/web/src/features/controlLayers/store/layersReducers.ts index 1199780818..6308f0d2ea 100644 --- a/invokeai/frontend/web/src/features/controlLayers/store/layersReducers.ts +++ b/invokeai/frontend/web/src/features/controlLayers/store/layersReducers.ts @@ -15,6 +15,7 @@ import type { ImageObjectAddedArg, LayerEntity, PointAddedToLineArg, + RectShape, RectShapeAddedArg, ScaleChangedArg, } from './types'; @@ -176,6 +177,17 @@ export const layersReducers = { layer.bboxNeedsUpdate = true; state.layers.imageCache = null; }, + layerRectShapeAdded2: (state, action: PayloadAction<{ id: string; rectShape: RectShape }>) => { + const { id, rectShape } = action.payload; + const layer = selectLayer(state, id); + if (!layer) { + return; + } + + layer.objects.push(rectShape); + layer.bboxNeedsUpdate = true; + state.layers.imageCache = null; + }, layerBrushLineAdded: { reducer: (state, action: PayloadAction) => { const { id, points, lineId, color, width, clip } = action.payload; diff --git a/invokeai/frontend/web/src/features/controlLayers/store/regionsReducers.ts b/invokeai/frontend/web/src/features/controlLayers/store/regionsReducers.ts index b033d6f813..2fd0e69cfd 100644 --- a/invokeai/frontend/web/src/features/controlLayers/store/regionsReducers.ts +++ b/invokeai/frontend/web/src/features/controlLayers/store/regionsReducers.ts @@ -7,6 +7,7 @@ import type { CLIPVisionModelV2, EraserLine, IPMethodV2, + RectShape, ScaleChangedArg, } from 'features/controlLayers/store/types'; import { imageDTOToImageObject, imageDTOToImageWithDims, RGBA_RED } from 'features/controlLayers/store/types'; @@ -383,6 +384,17 @@ export const regionsReducers = { rg.bboxNeedsUpdate = true; state.layers.imageCache = null; }, + rgRectShapeAdded2: (state, action: PayloadAction<{ id: string; rectShape: RectShape }>) => { + const { id, rectShape } = action.payload; + const rg = selectRG(state, id); + if (!rg) { + return; + } + + rg.objects.push(rectShape); + rg.bboxNeedsUpdate = true; + state.layers.imageCache = null; + }, rgEraserLineAdded: { reducer: (state, action: PayloadAction) => { const { id, points, lineId, width, clip } = action.payload;