diff --git a/CHANGELOG.md b/CHANGELOG.md index 7443a4cd41d..f4dae30e75a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Make sure frame unzip web worker correctly terminates after unzipping all images in a requested chunk () - Reset password link was unavailable before login () - Manifest: migration () +- Fixed cropping polygon in some corner cases () ### Security diff --git a/cvat-canvas/package-lock.json b/cvat-canvas/package-lock.json index 18942f7acdf..4419de0763b 100644 --- a/cvat-canvas/package-lock.json +++ b/cvat-canvas/package-lock.json @@ -1,6 +1,6 @@ { "name": "cvat-canvas", - "version": "2.4.2", + "version": "2.4.3", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/cvat-canvas/package.json b/cvat-canvas/package.json index 727b2c14cee..9612021da4f 100644 --- a/cvat-canvas/package.json +++ b/cvat-canvas/package.json @@ -1,6 +1,6 @@ { "name": "cvat-canvas", - "version": "2.4.2", + "version": "2.4.3", "description": "Part of Computer Vision Annotation Tool which presents its canvas library", "main": "src/canvas.ts", "scripts": { diff --git a/cvat-canvas/src/typescript/drawHandler.ts b/cvat-canvas/src/typescript/drawHandler.ts index d1dc1cc40b7..f3e08e07d5b 100644 --- a/cvat-canvas/src/typescript/drawHandler.ts +++ b/cvat-canvas/src/typescript/drawHandler.ts @@ -32,6 +32,11 @@ export interface DrawHandler { cancel(): void; } +interface FinalCoordinates { + points: number[]; + box: Box; +} + export class DrawHandlerImpl implements DrawHandler { // callback is used to notify about creating new shape private onDrawDone: (data: object | null, duration?: number, continueDraw?: boolean) => void; @@ -73,19 +78,14 @@ export class DrawHandlerImpl implements DrawHandler { return [xtl, ytl, xbr, ybr]; } - private getFinalPolyshapeCoordinates( - targetPoints: number[], - ): { - points: number[]; - box: Box; - } { + private getFinalPolyshapeCoordinates(targetPoints: number[]): FinalCoordinates { const { offset } = this.geometry; let points = targetPoints.map((coord: number): number => coord - offset); const box = { xtl: Number.MAX_SAFE_INTEGER, ytl: Number.MAX_SAFE_INTEGER, - xbr: Number.MAX_SAFE_INTEGER, - ybr: Number.MAX_SAFE_INTEGER, + xbr: Number.MIN_SAFE_INTEGER, + ybr: Number.MIN_SAFE_INTEGER, }; const frameWidth = this.geometry.image.width; @@ -96,9 +96,9 @@ export class DrawHandlerImpl implements DrawHandler { Vertical, } - const isBetween = (x1: number, x2: number, c: number): boolean => ( - c >= Math.min(x1, x2) && c <= Math.max(x1, x2) - ); + function isBetween(x1: number, x2: number, c: number): boolean { + return c >= Math.min(x1, x2) && c <= Math.max(x1, x2); + } const isInsideFrame = (p: Point, direction: Direction): boolean => { if (direction === Direction.Horizontal) { @@ -121,22 +121,35 @@ export class DrawHandlerImpl implements DrawHandler { const findIntersectionsWithFrameBorders = (p1: Point, p2: Point, direction: Direction): number[] => { const resultPoints = []; + const leftLine = [ + { x: 0, y: 0 }, + { x: 0, y: frameHeight }, + ]; + const topLine = [ + { x: frameWidth, y: 0 }, + { x: 0, y: 0 }, + ]; + const rightLine = [ + { x: frameWidth, y: frameHeight }, + { x: frameWidth, y: 0 }, + ]; + const bottomLine = [ + { x: 0, y: frameHeight }, + { x: frameWidth, y: frameHeight }, + ]; + if (direction === Direction.Horizontal) { - resultPoints.push(...findInersection(p1, p2, { x: 0, y: 0 }, { x: 0, y: frameHeight })); - resultPoints.push( - ...findInersection(p1, p2, { x: frameWidth, y: frameHeight }, { x: frameWidth, y: 0 }), - ); + resultPoints.push(...findInersection(p1, p2, leftLine[0], leftLine[1])); + resultPoints.push(...findInersection(p1, p2, rightLine[0], rightLine[1])); } else { - resultPoints.push( - ...findInersection(p1, p2, { x: 0, y: frameHeight }, { x: frameWidth, y: frameHeight }), - ); - resultPoints.push(...findInersection(p1, p2, { x: frameWidth, y: 0 }, { x: 0, y: 0 })); + resultPoints.push(...findInersection(p1, p2, bottomLine[0], bottomLine[1])); + resultPoints.push(...findInersection(p1, p2, topLine[0], topLine[1])); } if (resultPoints.length === 4) { if ( - Math.sign(resultPoints[0] - resultPoints[2]) !== Math.sign(p1.x - p2.x) - && Math.sign(resultPoints[1] - resultPoints[3]) !== Math.sign(p1.y - p2.y) + (p1.x === p2.x || Math.sign(resultPoints[0] - resultPoints[2]) !== Math.sign(p1.x - p2.x)) + && (p1.y === p2.y || Math.sign(resultPoints[1] - resultPoints[3]) !== Math.sign(p1.y - p2.y)) ) { [resultPoints[0], resultPoints[2]] = [resultPoints[2], resultPoints[0]]; [resultPoints[1], resultPoints[3]] = [resultPoints[3], resultPoints[1]]; @@ -145,24 +158,23 @@ export class DrawHandlerImpl implements DrawHandler { return resultPoints; }; - const crop = (polygonPoints: number[], direction: Direction): number[] => { + const crop = (shapePoints: number[], direction: Direction): number[] => { const resultPoints = []; - for (let i = 0; i < polygonPoints.length - 1; i += 2) { - const curPoint = { x: polygonPoints[i], y: polygonPoints[i + 1] }; + const isPolyline = this.drawData.shapeType === 'polyline'; + const isPolygon = this.drawData.shapeType === 'polygon'; + + for (let i = 0; i < shapePoints.length - 1; i += 2) { + const curPoint = { x: shapePoints[i], y: shapePoints[i + 1] }; if (isInsideFrame(curPoint, direction)) { - resultPoints.push(polygonPoints[i], polygonPoints[i + 1]); + resultPoints.push(shapePoints[i], shapePoints[i + 1]); } - const isLastPoint = i === polygonPoints.length - 2; - if ( - isLastPoint - && (this.drawData.shapeType === 'polyline' - || (this.drawData.shapeType === 'polygon' && polygonPoints.length === 4)) - ) { + const isLastPoint = i === shapePoints.length - 2; + if (isLastPoint && (isPolyline || (isPolygon && shapePoints.length === 4))) { break; } const nextPoint = isLastPoint - ? { x: polygonPoints[0], y: polygonPoints[1] } - : { x: polygonPoints[i + 2], y: polygonPoints[i + 3] }; + ? { x: shapePoints[0], y: shapePoints[1] } + : { x: shapePoints[i + 2], y: shapePoints[i + 3] }; const intersectionPoints = findIntersectionsWithFrameBorders(curPoint, nextPoint, direction); if (intersectionPoints.length !== 0) { resultPoints.push(...intersectionPoints); @@ -187,20 +199,15 @@ export class DrawHandlerImpl implements DrawHandler { }; } - private getFinalCuboidCoordinates( - targetPoints: number[], - ): { - points: number[]; - box: Box; - } { + private getFinalCuboidCoordinates(targetPoints: number[]): FinalCoordinates { const { offset } = this.geometry; let points = targetPoints; const box = { - xtl: 0, - ytl: 0, - xbr: Number.MAX_SAFE_INTEGER, - ybr: Number.MAX_SAFE_INTEGER, + xtl: Number.MAX_SAFE_INTEGER, + ytl: Number.MAX_SAFE_INTEGER, + xbr: Number.MIN_SAFE_INTEGER, + ybr: Number.MIN_SAFE_INTEGER, }; const frameWidth = this.geometry.image.width; @@ -238,27 +245,34 @@ export class DrawHandlerImpl implements DrawHandler { if (cuboidOffsets.length === points.length / 2) { cuboidOffsets.forEach((offsetCoords: number[]): void => { - if (Math.sqrt((offsetCoords[0] ** 2) + (offsetCoords[1] ** 2)) < minCuboidOffset.d) { - minCuboidOffset.d = Math.sqrt((offsetCoords[0] ** 2) + (offsetCoords[1] ** 2)); + const dx = offsetCoords[0] ** 2; + const dy = offsetCoords[1] ** 2; + if (Math.sqrt(dx + dy) < minCuboidOffset.d) { + minCuboidOffset.d = Math.sqrt(dx + dy); [minCuboidOffset.dx, minCuboidOffset.dy] = offsetCoords; } }); points = points.map((coord: number, i: number): number => { - const finalCoord = coord + (i % 2 === 0 ? minCuboidOffset.dx : minCuboidOffset.dy); - - if (i % 2 === 0) { - box.xtl = Math.max(box.xtl, finalCoord); - box.xbr = Math.min(box.xbr, finalCoord); - } else { - box.ytl = Math.max(box.ytl, finalCoord); - box.ybr = Math.min(box.ybr, finalCoord); + if (i % 2) { + return coord + minCuboidOffset.dy; } - - return finalCoord; + return coord + minCuboidOffset.dx; }); } + points.forEach((coord: number, i: number): number => { + if (i % 2 === 0) { + box.xtl = Math.min(box.xtl, coord); + box.xbr = Math.max(box.xbr, coord); + } else { + box.ytl = Math.min(box.ytl, coord); + box.ybr = Math.max(box.ybr, coord); + } + + return coord; + }); + return { points: points.map((coord: number): number => coord - offset), box, @@ -449,8 +463,9 @@ export class DrawHandlerImpl implements DrawHandler { } else { this.drawInstance.draw('update', e); const deltaTreshold = 15; - const delta = Math.sqrt(((e.clientX - lastDrawnPoint.x) ** 2) - + ((e.clientY - lastDrawnPoint.y) ** 2)); + const dx = (e.clientX - lastDrawnPoint.x) ** 2; + const dy = (e.clientY - lastDrawnPoint.y) ** 2; + const delta = Math.sqrt(dx + dy); if (delta > deltaTreshold) { this.drawInstance.draw('point', e); } @@ -482,36 +497,15 @@ export class DrawHandlerImpl implements DrawHandler { && (box.xbr - box.xtl) * (box.ybr - box.ytl) >= consts.AREA_THRESHOLD && points.length >= 3 * 2 ) { - this.onDrawDone( - { - clientID, - shapeType, - points, - }, - Date.now() - this.startTimestamp, - ); + this.onDrawDone({ clientID, shapeType, points }, Date.now() - this.startTimestamp); } else if ( shapeType === 'polyline' && (box.xbr - box.xtl >= consts.SIZE_THRESHOLD || box.ybr - box.ytl >= consts.SIZE_THRESHOLD) && points.length >= 2 * 2 ) { - this.onDrawDone( - { - clientID, - shapeType, - points, - }, - Date.now() - this.startTimestamp, - ); + this.onDrawDone({ clientID, shapeType, points }, Date.now() - this.startTimestamp); } else if (shapeType === 'points' && (e.target as any).getAttribute('points') !== '0,0') { - this.onDrawDone( - { - clientID, - shapeType, - points, - }, - Date.now() - this.startTimestamp, - ); + this.onDrawDone({ clientID, shapeType, points }, Date.now() - this.startTimestamp); // TODO: think about correct constraign for cuboids } else if (shapeType === 'cuboid' && points.length === 4 * 2) { this.onDrawDone( diff --git a/cvat-canvas/tsconfig.json b/cvat-canvas/tsconfig.json index 5b7154934a7..700dfe36889 100644 --- a/cvat-canvas/tsconfig.json +++ b/cvat-canvas/tsconfig.json @@ -3,7 +3,7 @@ "baseUrl": ".", "emitDeclarationOnly": true, "module": "es6", - "target": "es6", + "target": "es2016", "noImplicitAny": true, "preserveConstEnums": true, "declaration": true,