diff --git a/changelog.d/20240521_132410_krishavrajsingh_endPolyline.md b/changelog.d/20240521_132410_krishavrajsingh_endPolyline.md new file mode 100644 index 000000000000..6f9a5289a9a8 --- /dev/null +++ b/changelog.d/20240521_132410_krishavrajsingh_endPolyline.md @@ -0,0 +1,4 @@ +### Added + +- Polyline editing may be finished using corresponding shortcut + () diff --git a/cvat-canvas/src/typescript/canvas.ts b/cvat-canvas/src/typescript/canvas.ts index 84e4bca31e4b..1bcc7ddb961a 100644 --- a/cvat-canvas/src/typescript/canvas.ts +++ b/cvat-canvas/src/typescript/canvas.ts @@ -11,6 +11,7 @@ import { CanvasModel, CanvasModelImpl, RectDrawingMethod, CuboidDrawingMethod, Configuration, Geometry, Mode, HighlightSeverity as _HighlightSeverity, CanvasHint as _CanvasHint, + PolyEditData, } from './canvasModel'; import { Master } from './master'; import { CanvasController, CanvasControllerImpl } from './canvasController'; @@ -35,7 +36,7 @@ interface Canvas { interact(interactionData: InteractionData): void; draw(drawData: DrawData): void; - edit(editData: MasksEditData): void; + edit(editData: MasksEditData | PolyEditData): void; group(groupData: GroupData): void; join(joinData: JoinData): void; slice(sliceData: SliceData): void; @@ -137,7 +138,7 @@ class CanvasImpl implements Canvas { this.model.draw(drawData); } - public edit(editData: MasksEditData): void { + public edit(editData: MasksEditData | PolyEditData): void { this.model.edit(editData); } diff --git a/cvat-canvas/src/typescript/canvasController.ts b/cvat-canvas/src/typescript/canvasController.ts index 9acf58e95fc2..82ec611be3e4 100644 --- a/cvat-canvas/src/typescript/canvasController.ts +++ b/cvat-canvas/src/typescript/canvasController.ts @@ -20,6 +20,7 @@ import { Configuration, MasksEditData, HighlightedElements, + PolyEditData, } from './canvasModel'; export interface CanvasController { @@ -30,7 +31,7 @@ export interface CanvasController { readonly activeElement: ActiveElement; readonly highlightedElements: HighlightedElements; readonly drawData: DrawData; - readonly editData: MasksEditData; + readonly editData: MasksEditData | PolyEditData; readonly interactionData: InteractionData; readonly mergeData: MergeData; readonly splitData: SplitData; @@ -44,7 +45,7 @@ export interface CanvasController { zoom(x: number, y: number, direction: number): void; draw(drawData: DrawData): void; - edit(editData: MasksEditData): void; + edit(editData: MasksEditData | PolyEditData): void; enableDrag(x: number, y: number): void; drag(x: number, y: number): void; disableDrag(): void; @@ -96,7 +97,7 @@ export class CanvasControllerImpl implements CanvasController { this.model.draw(drawData); } - public edit(editData: MasksEditData): void { + public edit(editData: MasksEditData | PolyEditData): void { this.model.edit(editData); } @@ -136,7 +137,7 @@ export class CanvasControllerImpl implements CanvasController { return this.model.drawData; } - public get editData(): MasksEditData { + public get editData(): MasksEditData | PolyEditData { return this.model.editData; } diff --git a/cvat-canvas/src/typescript/canvasModel.ts b/cvat-canvas/src/typescript/canvasModel.ts index 426a76acfd62..181111396267 100644 --- a/cvat-canvas/src/typescript/canvasModel.ts +++ b/cvat-canvas/src/typescript/canvasModel.ts @@ -147,8 +147,8 @@ export interface InteractionResult { export interface PolyEditData { enabled: boolean; - state: any; - pointID: number; + state?: any; + pointID?: number; } export interface MasksEditData { @@ -249,7 +249,7 @@ export interface CanvasModel { readonly activeElement: ActiveElement; readonly highlightedElements: HighlightedElements; readonly drawData: DrawData; - readonly editData: MasksEditData; + readonly editData: MasksEditData | PolyEditData; readonly interactionData: InteractionData; readonly mergeData: MergeData; readonly splitData: SplitData; @@ -275,7 +275,7 @@ export interface CanvasModel { grid(stepX: number, stepY: number): void; draw(drawData: DrawData): void; - edit(editData: MasksEditData): void; + edit(editData: MasksEditData | PolyEditData): void; group(groupData: GroupData): void; join(joinData: JoinData): void; slice(sliceData: SliceData): void; @@ -369,7 +369,7 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel { fittedScale: number; zLayer: number | null; drawData: DrawData; - editData: MasksEditData; + editData: MasksEditData | PolyEditData; interactionData: InteractionData; mergeData: MergeData; groupData: GroupData; @@ -780,7 +780,7 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel { this.notify(UpdateReasons.DRAW); } - public edit(editData: MasksEditData): void { + public edit(editData: MasksEditData | PolyEditData): void { if (![Mode.IDLE, Mode.EDIT].includes(this.data.mode)) { throw Error(`Canvas is busy. Action: ${this.data.mode}`); } @@ -1083,7 +1083,7 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel { return { ...this.data.drawData }; } - public get editData(): MasksEditData { + public get editData(): MasksEditData | PolyEditData { return { ...this.data.editData }; } diff --git a/cvat-canvas/src/typescript/canvasView.ts b/cvat-canvas/src/typescript/canvasView.ts index 4b348903aae3..398a6262c5d3 100644 --- a/cvat-canvas/src/typescript/canvasView.ts +++ b/cvat-canvas/src/typescript/canvasView.ts @@ -1642,6 +1642,8 @@ export class CanvasViewImpl implements CanvasView, Listener { this.masksHandler.edit(data); } else if (this.masksHandler.enabled) { this.masksHandler.edit(data); + } else if (this.editHandler.enabled && this.editHandler.shapeType === 'polyline') { + this.editHandler.edit(data); } } else if (reason === UpdateReasons.INTERACT) { const data: InteractionData = this.controller.interactionData; @@ -1854,18 +1856,18 @@ export class CanvasViewImpl implements CanvasView, Listener { const { points } = state; const [left, top, right, bottom] = points.slice(-4); const imageBitmap = expandChannels(255, 255, 255, points); - imageDataToDataURL(imageBitmap, right - left + 1, bottom - top + 1, - (dataURL: string) => new Promise((resolve) => { - if (bitmapUpdateReqId === this.bitmapUpdateReqId) { - const img = document.createElement('img'); - img.addEventListener('load', () => { - ctx.drawImage(img, left, top); - URL.revokeObjectURL(dataURL); - resolve(); - }); - img.src = dataURL; - } - })); + imageDataToDataURL(imageBitmap, right - left + 1, bottom - top + 1, (dataURL: string) => new + Promise((resolve) => { + if (bitmapUpdateReqId === this.bitmapUpdateReqId) { + const img = document.createElement('img'); + img.addEventListener('load', () => { + ctx.drawImage(img, left, top); + URL.revokeObjectURL(dataURL); + resolve(); + }); + img.src = dataURL; + } + })); } if (state.shapeType === 'cuboid') { diff --git a/cvat-canvas/src/typescript/editHandler.ts b/cvat-canvas/src/typescript/editHandler.ts index 89e61881a57c..567eea29c7de 100644 --- a/cvat-canvas/src/typescript/editHandler.ts +++ b/cvat-canvas/src/typescript/editHandler.ts @@ -16,6 +16,8 @@ export interface EditHandler { transform(geometry: Geometry): void; configurate(configuration: Configuration): void; cancel(): void; + enabled: boolean; + shapeType: string; } export class EditHandlerImpl implements EditHandler { @@ -31,9 +33,9 @@ export class EditHandlerImpl implements EditHandler { private autobordersEnabled: boolean; private intelligentCutEnabled: boolean; private outlinedBorders: string; + private isEditing: boolean; private setupTrailingPoint(circle: SVG.Circle): void { - const head = this.editedShape.attr('points').split(' ').slice(0, this.editData.pointID).join(' '); circle.on('mouseenter', (): void => { circle.attr({ 'stroke-width': consts.POINTS_SELECTED_STROKE_WIDTH / this.geometry.scale, @@ -46,22 +48,9 @@ export class EditHandlerImpl implements EditHandler { }); }); - const minimumPoints = 2; circle.on('mousedown', (e: MouseEvent): void => { if (e.button !== 0) return; - const { offset } = this.geometry; - const stringifiedPoints = `${head} ${this.editLine.node.getAttribute('points').slice(0, -2)}`; - const points = pointsToNumberArray(stringifiedPoints) - .slice(0, -2) - .map((coord: number): number => coord - offset); - - if (points.length >= minimumPoints * 2) { - const { state } = this.editData; - this.edit({ - enabled: false, - }); - this.onEditDone(state, points); - } + this.edit({ enabled: false }); }); } @@ -345,6 +334,7 @@ export class EditHandlerImpl implements EditHandler { this.canvas.off('mousedown.edit'); this.canvas.off('mousemove.edit'); this.autoborderHandler.autoborder(false); + this.isEditing = false; if (this.editedShape) { this.setupPoints(false); @@ -372,6 +362,7 @@ export class EditHandlerImpl implements EditHandler { .clone().attr('stroke', this.outlinedBorders); this.setupPoints(true); this.startEdit(); + this.isEditing = true; // draw points for this with selected and start editing till another point is clicked // click one of two parts to remove (in case of polygon only) @@ -380,6 +371,18 @@ export class EditHandlerImpl implements EditHandler { } private closeEditing(): void { + if (this.isEditing && this.editData.state.shapeType === 'polyline') { + const { offset } = this.geometry; + const head = this.editedShape.attr('points').split(' ').slice(0, this.editData.pointID).join(' '); + const stringifiedPoints = `${head} ${this.editLine.node.getAttribute('points').slice(0, -2)}`; + const points = pointsToNumberArray(stringifiedPoints) + .slice(0, -2) + .map((coord: number): number => coord - offset); + if (points.length >= 2 * 2) { // minimumPoints * 2 + const { state } = this.editData; + this.onEditDone(state, points); + } + } this.release(); } @@ -400,11 +403,12 @@ export class EditHandlerImpl implements EditHandler { this.editLine = null; this.geometry = null; this.clones = []; + this.isEditing = false; } public edit(editData: any): void { if (editData.enabled) { - if (editData.state.shapeType !== 'rectangle') { + if (['polygon', 'polyline', 'points'].includes(editData.state.shapeType)) { this.editData = editData; this.initEditing(); } else { @@ -421,6 +425,14 @@ export class EditHandlerImpl implements EditHandler { this.onEditDone(null, null); } + get enabled(): boolean { + return this.isEditing; + } + + get shapeType(): string { + return this.editData.state.shapeType; + } + public configurate(configuration: Configuration): void { this.autobordersEnabled = configuration.autoborders; this.outlinedBorders = configuration.outlinedBorders || 'black'; diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/controls-side-bar.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/controls-side-bar.tsx index 8ebbfa893ace..f516a2065fab 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/controls-side-bar.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/controls-side-bar.tsx @@ -165,7 +165,7 @@ export default function ControlsSideBarComponent(props: Props): JSX.Element { if (!drawing) { if (editing) { // users probably will press N as they are used to do when they want to finish editing - // in this case, if a mask is being edited we probably want to finish editing first + // in this case, if a mask or polyline is being edited we probably want to finish editing first canvasInstance.edit({ enabled: false }); return; }