TS Module from CVAT-Canvas (#663)

* Fixed drawn coordinates
* TypeScript build
* A couple of fixes
* Removed extra changes from webpack
* Init grouper, dumper and merger
* Removed extra aliases (they were used for test only)
main
Boris Sekachev 7 years ago committed by Nikita Manovich
parent e15340d625
commit 768c254c9e

@ -1,10 +1,8 @@
# Module CVAT-CANVAS # Module CVAT-CANVAS
## Description ## Description
The CVAT module presents a canvas to viewing, drawing and editing of annotations. The CVAT module written in TypeScript language.
It presents a canvas to viewing, drawing and editing of annotations.
- It has been written on typescript
- It contains the class ```Canvas``` and the enum ```Rotation```
## Commands ## Commands
- Building of the module from sources in the ```dist``` directory: - Building of the module from sources in the ```dist``` directory:
@ -21,15 +19,7 @@ npm version minor # updated after major changes which don't affect API compati
npm version major # updated after major changes which affect API compatibility with previous versions npm version major # updated after major changes which affect API compatibility with previous versions
``` ```
## Creation ## Using
Canvas is created by using constructor:
```js
const { Canvas } = require('./canvas');
const canvas = new Canvas(ObjectStateClass);
```
- Canvas has transparent background
Canvas itself handles: Canvas itself handles:
- Shape context menu (PKM) - Shape context menu (PKM)
@ -39,12 +29,14 @@ Canvas itself handles:
- Remove point (PKM) - Remove point (PKM)
- Polyshape editing (Shift + LKM) - Polyshape editing (Shift + LKM)
## API ### API Methods
### Methods
All methods are sync.
```ts ```ts
enum Rotation {
ANTICLOCKWISE90,
CLOCKWISE90,
}
interface DrawData { interface DrawData {
enabled: boolean; enabled: boolean;
shapeType?: string; shapeType?: string;
@ -53,23 +45,38 @@ All methods are sync.
crosshair?: boolean; crosshair?: boolean;
} }
html(): HTMLDivElement; interface GroupData {
setup(frameData: FrameData, objectStates: ObjectState): void; enabled: boolean;
activate(clientID: number, attributeID?: number): void; resetGroup?: boolean;
rotate(rotation: Rotation, remember?: boolean): void; }
focus(clientID: number, padding?: number): void;
fit(): void;
grid(stepX: number, stepY: number): void;
draw(drawData: DrawData): void; interface MergeData {
split(enabled?: boolean): void; enabled: boolean;
group(enabled?: boolean): void; }
merge(enabled?: boolean): void;
interface SplitData {
enabled: boolean;
}
cancel(): any; interface Canvas {
html(): HTMLDivElement;
setup(frameData: any, objectStates: any[]): void;
activate(clientID: number, attributeID?: number): void;
rotate(rotation: Rotation, remember?: boolean): void;
focus(clientID: number, padding?: number): void;
fit(): void;
grid(stepX: number, stepY: number): void;
draw(drawData: DrawData): void;
group(groupData: GroupData): void;
split(splitData: SplitData): void;
merge(mergeData: MergeData): void;
cancel(): void;
}
``` ```
### CSS Classes/IDs ### API CSS
- All drawn objects (shapes, tracks) have an id ```cvat_canvas_object_{objectState.id}``` - All drawn objects (shapes, tracks) have an id ```cvat_canvas_object_{objectState.id}```
- Drawn shapes and tracks have classes ```cvat_canvas_shape```, - Drawn shapes and tracks have classes ```cvat_canvas_shape```,
@ -90,15 +97,72 @@ Standard JS events are used.
- canvas.setup - canvas.setup
- canvas.activated => ObjectState - canvas.activated => ObjectState
- canvas.deactivated - canvas.deactivated
- canvas.moved => [ObjectState], x, y - canvas.moved => {states: ObjectState[], x: number, y: number}
- canvas.drawn => ObjectState - canvas.drawn => {state: ObjectState}
- canvas.edited => ObjectState - canvas.edited => {state: ObjectState}
- canvas.splitted => ObjectState - canvas.splitted => {state: ObjectState, frame: number}
- canvas.groupped => [ObjectState] - canvas.groupped => {states: ObjectState[], reset: boolean}
- canvas.merged => [ObjectState] - canvas.merged => {states: ObjectState[]}
- canvas.canceled - canvas.canceled
``` ```
### WEB
```js
// Create an instance of a canvas
const canvas = new window.canvas.Canvas(window.cvat.classes.ObjectState);
// Put canvas to a html container
htmlContainer.appendChild(canvas.html());
// Next you can use its API methods. For example:
canvas.rotate(window.Canvas.Rotation.CLOCKWISE90);
canvas.draw({
enabled: true,
shapeType: 'rectangle',
crosshair: true,
});
```
### TypeScript
- Add to ```tsconfig.json```:
```json
"compilerOptions": {
"paths": {
"cvat-canvas.node": ["3rdparty/cvat-canvas.node"]
}
}
```
- ```3rdparty``` directory contains both ```cvat-canvas.node.js``` and ```cvat-canvas.node.d.ts```.
- Add alias to ```webpack.config.js```:
```js
module.exports = {
resolve: {
alias: {
'cvat-canvas.node': path.resolve(__dirname, '3rdparty/cvat-canvas.node.js'),
}
}
}
```
Than you can use it in TypeScript:
```ts
import * as CANVAS from 'cvat-canvas.node';
// Create an instance of a canvas
const canvas = new CANVAS.Canvas(null);
// Put canvas to a html container
htmlContainer.appendChild(canvas.html());
// Next you can use its API methods. For example:
canvas.rotate(CANVAS.Rotation.CLOCKWISE90);
canvas.draw({
enabled: true,
shapeType: 'rectangle',
crosshair: true,
});
```
## States ## States
![](images/states.svg) ![](images/states.svg)

@ -2,7 +2,7 @@
"name": "cvat-canvas", "name": "cvat-canvas",
"version": "0.1.0", "version": "0.1.0",
"description": "Part of Computer Vision Annotation Tool which presents its canvas library", "description": "Part of Computer Vision Annotation Tool which presents its canvas library",
"main": "babel.config.js", "main": "src/canvas.ts",
"scripts": { "scripts": {
"build": "tsc && webpack --config ./webpack.config.js", "build": "tsc && webpack --config ./webpack.config.js",
"server": "nodemon --watch config --exec 'webpack-dev-server --config ./webpack.config.js --mode=development --open'" "server": "nodemon --watch config --exec 'webpack-dev-server --config ./webpack.config.js --mode=development --open'"
@ -25,6 +25,7 @@
"@typescript-eslint/eslint-plugin": "^1.13.0", "@typescript-eslint/eslint-plugin": "^1.13.0",
"@typescript-eslint/parser": "^1.13.0", "@typescript-eslint/parser": "^1.13.0",
"babel-loader": "^8.0.6", "babel-loader": "^8.0.6",
"dts-bundle-webpack": "^1.0.2",
"eslint": "^6.1.0", "eslint": "^6.1.0",
"eslint-config-airbnb-typescript": "^4.0.1", "eslint-config-airbnb-typescript": "^4.0.1",
"eslint-config-typescript-recommended": "^1.4.17", "eslint-config-typescript-recommended": "^1.4.17",

@ -0,0 +1,122 @@
.cvat_canvas_hidden {
display: none;
}
.cvat_canvas_shape {
fill-opacity: 0.1;
stroke-opacity: 1;
}
polyline.cvat_canvas_shape {
fill-opacity: 0;
stroke-opacity: 1;
}
.cvat_canvas_text {
font-weight: bold;
font-size: 1.2em;
fill: white;
cursor: default;
font-family: Calibri, Candara, Segoe, "Segoe UI", Optima, Arial, sans-serif;
text-shadow: 0px 0px 4px black;
user-select: none;
pointer-events: none;
}
.cvat_canvas_crosshair {
stroke: red;
}
.cvat_canvas_shape_activated {
}
.cvat_canvas_shape_grouping {
}
.cvat_canvas_shape_merging {
}
.cvat_canvas_shape_drawing {
fill-opacity: 0.1;
stroke-opacity: 1;
fill: white;
stroke: black;
}
.svg_select_boundingRect {
opacity: 0;
pointer-events: none;
}
#cvat_canvas_wrapper {
width: 100%;
height: 93%;
border-radius: 5px;
background-color: white;
overflow: hidden;
position: relative;
}
#cvat_canvas_loading_animation {
z-index: 1;
position: absolute;
width: 100%;
height: 100%;
}
#cvat_canvas_loading_circle {
fill-opacity: 0;
stroke: #09c;
stroke-width: 3px;
stroke-dasharray: 50;
animation: loadingAnimation 1s linear infinite;
}
#cvat_canvas_text_content {
position: absolute;
z-index: 3;
pointer-events: none;
width: 100%;
height: 100%;
pointer-events: none;
}
#cvat_canvas_background {
position: absolute;
z-index: 0;
background-repeat: no-repeat;
width: 100%;
height: 100%;
box-shadow: 2px 2px 5px 0px rgba(0,0,0,0.75);
}
#cvat_canvas_grid {
position: absolute;
z-index: 2;
pointer-events: none;
width: 100%;
height: 100%;
pointer-events: none;
}
#cvat_canvas_grid_pattern {
opacity: 1;
stroke: white;
}
#cvat_canvas_content {
position: absolute;
z-index: 2;
outline: 10px solid black;
width: 100%;
height: 100%;
}
@keyframes loadingAnimation {
0% {stroke-dashoffset: 1; stroke: #09c;}
50% {stroke-dashoffset: 100; stroke: #f44;}
100% {stroke-dashoffset: 300; stroke: #09c;}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -4,12 +4,19 @@
*/ */
import { import {
CanvasModel,
CanvasModelImpl,
Rotation, Rotation,
DrawData, DrawData,
MergeData,
SplitData,
GroupData,
CanvasModel,
CanvasModelImpl,
} from './canvasModel'; } from './canvasModel';
import {
Master,
} from './master';
import { import {
CanvasController, CanvasController,
CanvasControllerImpl, CanvasControllerImpl,
@ -20,6 +27,7 @@ import {
CanvasViewImpl, CanvasViewImpl,
} from './canvasView'; } from './canvasView';
interface Canvas { interface Canvas {
html(): HTMLDivElement; html(): HTMLDivElement;
setup(frameData: any, objectStates: any[]): void; setup(frameData: any, objectStates: any[]): void;
@ -30,15 +38,15 @@ interface Canvas {
grid(stepX: number, stepY: number): void; grid(stepX: number, stepY: number): void;
draw(drawData: DrawData): void; draw(drawData: DrawData): void;
split(enabled?: boolean): void; group(groupData: GroupData): void;
group(enabled?: boolean): void; split(splitData: SplitData): void;
merge(enabled?: boolean): void; merge(mergeData: MergeData): void;
cancel(): void; cancel(): void;
} }
class CanvasImpl implements Canvas { class CanvasImpl implements Canvas {
private model: CanvasModel; private model: CanvasModel & Master;
private controller: CanvasController; private controller: CanvasController;
private view: CanvasView; private view: CanvasView;
@ -60,7 +68,7 @@ class CanvasImpl implements Canvas {
this.model.activate(clientID, attributeID); this.model.activate(clientID, attributeID);
} }
public rotate(rotation: Rotation, remember: boolean): void { public rotate(rotation: Rotation, remember: boolean = false): void {
this.model.rotate(rotation, remember); this.model.rotate(rotation, remember);
} }
@ -80,16 +88,16 @@ class CanvasImpl implements Canvas {
this.model.draw(drawData); this.model.draw(drawData);
} }
public split(enabled: boolean = false): void { public split(splitData: SplitData): void {
this.model.split(enabled); this.model.split(splitData);
} }
public group(enabled: boolean = false): void { public group(groupData: GroupData): void {
this.model.group(enabled); this.model.group(groupData);
} }
public merge(enabled: boolean = false): void { public merge(mergeData: MergeData): void {
this.model.merge(enabled); this.model.merge(mergeData);
} }
public cancel(): void { public cancel(): void {
@ -97,6 +105,7 @@ class CanvasImpl implements Canvas {
} }
} }
export { export {
CanvasImpl as Canvas, CanvasImpl as Canvas,
Rotation, Rotation,

@ -47,6 +47,19 @@ export interface DrawData {
crosshair?: boolean; crosshair?: boolean;
} }
export interface GroupData {
enabled: boolean;
resetGroup: boolean;
}
export interface MergeData {
enabled: boolean;
}
export interface SplitData {
enabled: boolean;
}
export enum FrameZoom { export enum FrameZoom {
MIN = 0.1, MIN = 0.1,
MAX = 10, MAX = 10,
@ -67,9 +80,12 @@ export enum UpdateReasons {
FOCUS = 'focus', FOCUS = 'focus',
ACTIVATE = 'activate', ACTIVATE = 'activate',
DRAW = 'draw', DRAW = 'draw',
MERGE = 'merge',
SPLIT = 'split',
GROUP = 'group',
} }
export interface CanvasModel extends MasterImpl { export interface CanvasModel {
readonly image: string; readonly image: string;
readonly objects: any[]; readonly objects: any[];
readonly gridSize: Size; readonly gridSize: Size;
@ -90,9 +106,9 @@ export interface CanvasModel extends MasterImpl {
grid(stepX: number, stepY: number): void; grid(stepX: number, stepY: number): void;
draw(drawData: DrawData): void; draw(drawData: DrawData): void;
split(enabled: boolean): void; group(groupData: GroupData): void;
group(enabled: boolean): void; split(splitData: SplitData): void;
merge(enabled: boolean): void; merge(mergeData: MergeData): void;
cancel(): void; cancel(): void;
} }
@ -103,7 +119,6 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel {
activeElement: ActiveElement; activeElement: ActiveElement;
angle: number; angle: number;
canvasSize: Size; canvasSize: Size;
drawData: DrawData;
image: string; image: string;
imageOffset: number; imageOffset: number;
imageSize: Size; imageSize: Size;
@ -114,6 +129,10 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel {
rememberAngle: boolean; rememberAngle: boolean;
scale: number; scale: number;
top: number; top: number;
drawData: DrawData;
mergeData: MergeData;
groupData: GroupData;
splitData: SplitData;
}; };
public constructor(ObjectStateClass: any) { public constructor(ObjectStateClass: any) {
@ -129,12 +148,6 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel {
height: 0, height: 0,
width: 0, width: 0,
}, },
drawData: {
enabled: false,
shapeType: null,
numberOfPoints: null,
initialState: null,
},
image: '', image: '',
imageOffset: 0, imageOffset: 0,
imageSize: { imageSize: {
@ -155,6 +168,22 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel {
rememberAngle: false, rememberAngle: false,
scale: 1, scale: 1,
top: 0, top: 0,
drawData: {
enabled: false,
shapeType: null,
numberOfPoints: null,
initialState: null,
},
mergeData: {
enabled: false,
},
groupData: {
enabled: false,
resetGroup: false,
},
splitData: {
enabled: false,
},
}; };
} }
@ -209,10 +238,8 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel {
this.data.objects = objectStates; this.data.objects = objectStates;
this.notify(UpdateReasons.OBJECTS); this.notify(UpdateReasons.OBJECTS);
}).catch((exception: any): void => { }).catch((exception: any): void => {
console.log(exception.toString()); throw exception;
}); });
console.log(objectStates);
} }
public activate(clientID: number, attributeID: number): void { public activate(clientID: number, attributeID: number): void {
@ -300,16 +327,43 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel {
this.notify(UpdateReasons.DRAW); this.notify(UpdateReasons.DRAW);
} }
public split(enabled: boolean): any { public split(splitData: SplitData): void {
return enabled; if (this.data.splitData.enabled && splitData.enabled) {
return;
}
if (!this.data.splitData.enabled && !splitData.enabled) {
return;
}
this.data.splitData = splitData;
this.notify(UpdateReasons.SPLIT);
} }
public group(enabled: boolean): any { public group(groupData: GroupData): void {
return enabled; if (this.data.groupData.enabled && groupData.enabled) {
return;
}
if (!this.data.groupData.enabled && !groupData.enabled) {
return;
}
this.data.groupData = groupData;
this.notify(UpdateReasons.GROUP);
} }
public merge(enabled: boolean): any { public merge(mergeData: MergeData): void {
return enabled; if (this.data.mergeData.enabled && mergeData.enabled) {
return;
}
if (!this.data.mergeData.enabled && !mergeData.enabled) {
return;
}
this.data.mergeData = mergeData;
this.notify(UpdateReasons.MERGE);
} }
public cancel(): void { public cancel(): void {

@ -3,9 +3,6 @@
* SPDX-License-Identifier: MIT * SPDX-License-Identifier: MIT
*/ */
// Disable till full implementation
/* eslint class-methods-use-this: "off" */
import * as SVG from 'svg.js'; import * as SVG from 'svg.js';
// tslint:disable-next-line: ordered-imports // tslint:disable-next-line: ordered-imports
@ -16,6 +13,9 @@ import 'svg.select.js';
import { CanvasController } from './canvasController'; import { CanvasController } from './canvasController';
import { Listener, Master } from './master'; import { Listener, Master } from './master';
import { DrawHandler, DrawHandlerImpl } from './drawHandler'; import { DrawHandler, DrawHandlerImpl } from './drawHandler';
import { MergeHandler, MergeHandlerImpl } from './mergeHandler';
import { SplitHandler, SplitHandlerImpl } from './splitHandler';
import { GroupHandler, GroupHandlerImpl } from './groupHandler';
import { translateToSVG, translateFromSVG } from './shared'; import { translateToSVG, translateFromSVG } from './shared';
import consts from './consts'; import consts from './consts';
import { import {
@ -49,6 +49,48 @@ enum Mode {
DRAW = 'draw', DRAW = 'draw',
} }
function selectize(value: boolean, shape: SVG.Element, geometry: Geometry): void {
if (value) {
(shape as any).selectize(value, {
deepSelect: true,
pointSize: consts.BASE_POINT_SIZE / geometry.scale,
rotationPoint: false,
pointType(cx: number, cy: number): SVG.Circle {
const circle: SVG.Circle = this.nested
.circle(this.options.pointSize)
.stroke('black')
.fill(shape.node.getAttribute('fill'))
.center(cx, cy)
.attr({
'stroke-width': consts.BASE_STROKE_WIDTH / (3 * geometry.scale),
});
circle.node.addEventListener('mouseenter', (): void => {
circle.attr({
'stroke-width': circle.attr('stroke-width') * 2,
});
circle.addClass('cvat_canvas_selected_point');
});
circle.node.addEventListener('mouseleave', (): void => {
circle.attr({
'stroke-width': circle.attr('stroke-width') / 2,
});
circle.removeClass('cvat_canvas_selected_point');
});
return circle;
},
});
} else {
(shape as any).selectize(false, {
deepSelect: true,
});
}
}
function darker(color: string, percentage: number): string { function darker(color: string, percentage: number): string {
const R = Math.round(parseInt(color.slice(1, 3), 16) * (1 - percentage / 100)); const R = Math.round(parseInt(color.slice(1, 3), 16) * (1 - percentage / 100));
const G = Math.round(parseInt(color.slice(3, 5), 16) * (1 - percentage / 100)); const G = Math.round(parseInt(color.slice(3, 5), 16) * (1 - percentage / 100));
@ -78,6 +120,9 @@ export class CanvasViewImpl implements CanvasView, Listener {
private svgShapes: ShapeDict; private svgShapes: ShapeDict;
private svgTexts: TextDict; private svgTexts: TextDict;
private drawHandler: DrawHandler; private drawHandler: DrawHandler;
private mergeHandler: MergeHandler;
private splitHandler: SplitHandler;
private groupHandler: GroupHandler;
private activeElement: { private activeElement: {
state: any; state: any;
attributeID: number; attributeID: number;
@ -85,7 +130,7 @@ export class CanvasViewImpl implements CanvasView, Listener {
private mode: Mode; private mode: Mode;
private onDrawDone(data: Record<string, any>): void { private onDrawDone(data: object): void {
if (data) { if (data) {
const event: CustomEvent = new CustomEvent('canvas.drawn', { const event: CustomEvent = new CustomEvent('canvas.drawn', {
bubbles: false, bubbles: false,
@ -111,6 +156,70 @@ export class CanvasViewImpl implements CanvasView, Listener {
}); });
} }
private onMergeDone(objects: any[]): void {
if (objects) {
const event: CustomEvent = new CustomEvent('canvas.merged', {
bubbles: false,
cancelable: true,
detail: {
states: objects,
},
});
this.canvas.dispatchEvent(event);
} else {
const event: CustomEvent = new CustomEvent('canvas.canceled', {
bubbles: false,
cancelable: true,
});
this.canvas.dispatchEvent(event);
}
}
private onSplitDone(object: any): void {
if (object) {
const event: CustomEvent = new CustomEvent('canvas.splitted', {
bubbles: false,
cancelable: true,
detail: {
state: object,
},
});
this.canvas.dispatchEvent(event);
} else {
const event: CustomEvent = new CustomEvent('canvas.canceled', {
bubbles: false,
cancelable: true,
});
this.canvas.dispatchEvent(event);
}
}
private onGroupDone(objects: any[], reset: boolean): void {
if (objects) {
const event: CustomEvent = new CustomEvent('canvas.groupped', {
bubbles: false,
cancelable: true,
detail: {
states: objects,
reset,
},
});
this.canvas.dispatchEvent(event);
} else {
const event: CustomEvent = new CustomEvent('canvas.canceled', {
bubbles: false,
cancelable: true,
});
this.canvas.dispatchEvent(event);
}
}
public constructor(model: CanvasModel & Master, controller: CanvasController) { public constructor(model: CanvasModel & Master, controller: CanvasController) {
this.controller = controller; this.controller = controller;
this.svgShapes = {}; this.svgShapes = {};
@ -131,7 +240,7 @@ export class CanvasViewImpl implements CanvasView, Listener {
this.content = window.document.createElementNS('http://www.w3.org/2000/svg', 'svg'); this.content = window.document.createElementNS('http://www.w3.org/2000/svg', 'svg');
this.adoptedContent = (SVG.adopt((this.content as any as HTMLElement)) as SVG.Container); this.adoptedContent = (SVG.adopt((this.content as any as HTMLElement)) as SVG.Container);
this.drawHandler = new DrawHandlerImpl(this.onDrawDone.bind(this), this.adoptedContent);
this.canvas = window.document.createElement('div'); this.canvas = window.document.createElement('div');
const loadingCircle: SVGCircleElement = window.document const loadingCircle: SVGCircleElement = window.document
@ -203,6 +312,24 @@ export class CanvasViewImpl implements CanvasView, Listener {
this.canvas.addEventListener('animationstart', canvasFirstMounted); this.canvas.addEventListener('animationstart', canvasFirstMounted);
// Setup API handlers
this.drawHandler = new DrawHandlerImpl(
this.onDrawDone.bind(this),
this.adoptedContent,
this.background,
);
this.mergeHandler = new MergeHandlerImpl(
this.onMergeDone.bind(this),
);
this.splitHandler = new SplitHandlerImpl(
this.onSplitDone.bind(this),
);
this.groupHandler = new GroupHandlerImpl(
this.onGroupDone.bind(this),
);
// Setup event handlers
this.content.addEventListener('dblclick', (): void => { this.content.addEventListener('dblclick', (): void => {
self.controller.fit(); self.controller.fit();
}); });
@ -239,7 +366,7 @@ export class CanvasViewImpl implements CanvasView, Listener {
detail: { detail: {
x, x,
y, y,
objects: this.controller.objects, states: this.controller.objects,
}, },
}); });
@ -383,14 +510,31 @@ export class CanvasViewImpl implements CanvasView, Listener {
} }
function setupObjects(objects: any[], geometry: Geometry): void { function setupObjects(objects: any[], geometry: Geometry): void {
this.adoptedContent.clear();
const ctm = this.content.getScreenCTM() const ctm = this.content.getScreenCTM()
.inverse().multiply(this.background.getScreenCTM()); .inverse().multiply(this.background.getScreenCTM());
this.deactivate();
// TODO: Compute difference // TODO: Compute difference
// Instead of simple clearing let's remove all objects properly
for (const id of Object.keys(this.svgShapes)) {
if (id in this.svgTexts) {
this.svgTexts[id].remove();
}
this.svgShapes[id].remove();
}
this.svgTexts = {};
this.svgShapes = {};
this.addObjects(ctm, objects, geometry); this.addObjects(ctm, objects, geometry);
// TODO: Update objects // TODO: Update objects
// TODO: Delete objects // TODO: Delete objects
this.mergeHandler.updateObjects(objects);
this.groupHandler.updateObjects(objects);
this.splitHandler.updateObjects(objects);
} }
const { geometry } = this.controller; const { geometry } = this.controller;
@ -498,7 +642,7 @@ export class CanvasViewImpl implements CanvasView, Listener {
(shape as any).draggable(false); (shape as any).draggable(false);
if (state.shapeType !== 'points') { if (state.shapeType !== 'points') {
this.selectize(false, shape, null); selectize(false, shape, null);
} }
(shape as any).resize(false); (shape as any).resize(false);
@ -513,48 +657,6 @@ export class CanvasViewImpl implements CanvasView, Listener {
} }
} }
private selectize(value: boolean, shape: SVG.Element, geometry: Geometry): void {
if (value) {
(shape as any).selectize(value, {
deepSelect: true,
pointSize: consts.BASE_POINT_SIZE / geometry.scale,
rotationPoint: false,
pointType(cx: number, cy: number): SVG.Circle {
const circle: SVG.Circle = this.nested
.circle(this.options.pointSize)
.stroke('black')
.fill(shape.node.getAttribute('fill'))
.center(cx, cy)
.attr({
'stroke-width': consts.BASE_STROKE_WIDTH / (3 * geometry.scale),
});
circle.node.addEventListener('mouseenter', (): void => {
circle.attr({
'stroke-width': circle.attr('stroke-width') * 2,
});
circle.addClass('cvat_canvas_selected_point');
});
circle.node.addEventListener('mouseleave', (): void => {
circle.attr({
'stroke-width': circle.attr('stroke-width') / 2,
});
circle.removeClass('cvat_canvas_selected_point');
});
return circle;
},
});
} else {
(shape as any).selectize(false, {
deepSelect: true,
});
}
}
private activate(geometry: Geometry, activeElement: ActiveElement): void { private activate(geometry: Geometry, activeElement: ActiveElement): void {
// Check if other element have been already activated // Check if other element have been already activated
if (this.activeElement) { if (this.activeElement) {
@ -605,7 +707,7 @@ export class CanvasViewImpl implements CanvasView, Listener {
}); });
if (state.shapeType !== 'points') { if (state.shapeType !== 'points') {
this.selectize(true, shape, geometry); selectize(true, shape, geometry);
} }
(shape as any).resize().on('resizestart', (): void => { (shape as any).resize().on('resizestart', (): void => {
@ -731,12 +833,17 @@ export class CanvasViewImpl implements CanvasView, Listener {
zOrder: state.zOrder, zOrder: state.zOrder,
}).addClass('cvat_canvas_shape'); }).addClass('cvat_canvas_shape');
this.selectize(true, shape, geometry); selectize(true, shape, geometry);
shape.remove = function remove(): void {
this.selectize(false, shape);
shape.constructor.prototype.remove.call(shape);
}.bind(this);
shape.attr('fill', 'none'); shape.attr('fill', 'none');
return shape; return shape;
} }
/* eslint-disable-next-line */
private addTag(state: any, geometry: Geometry): void { private addTag(state: any, geometry: Geometry): void {
console.log(state, geometry); console.log(state, geometry);
} }

@ -23,8 +23,10 @@ export interface DrawHandler {
} }
export class DrawHandlerImpl implements DrawHandler { export class DrawHandlerImpl implements DrawHandler {
private onDrawDone: any; // callback is used to notify about creating new shape // callback is used to notify about creating new shape
private onDrawDone: (data: object) => void;
private canvas: SVG.Container; private canvas: SVG.Container;
private background: SVGSVGElement;
private crosshair: { private crosshair: {
x: SVG.Line; x: SVG.Line;
y: SVG.Line; y: SVG.Line;
@ -70,8 +72,12 @@ export class DrawHandlerImpl implements DrawHandler {
} else { } else {
this.drawInstance.draw('done'); this.drawInstance.draw('done');
} }
this.drawInstance.remove();
this.drawInstance = null; // We should check again because state can be changed in 'cancel' and 'done'
if (this.drawInstance) {
this.drawInstance.remove();
this.drawInstance = null;
}
} }
} }
@ -91,6 +97,11 @@ export class DrawHandlerImpl implements DrawHandler {
[bbox.x, bbox.y, bbox.x + bbox.width, bbox.y + bbox.height], [bbox.x, bbox.y, bbox.x + bbox.width, bbox.y + bbox.height],
); );
([xtl, ytl, xbr, ybr] = translateToSVG(
this.background,
[xtl, ytl, xbr, ybr],
));
xtl = Math.min(Math.max(xtl, 0), frameWidth); xtl = Math.min(Math.max(xtl, 0), frameWidth);
xbr = Math.min(Math.max(xbr, 0), frameWidth); xbr = Math.min(Math.max(xbr, 0), frameWidth);
ytl = Math.min(Math.max(ytl, 0), frameHeight); ytl = Math.min(Math.max(ytl, 0), frameHeight);
@ -178,7 +189,7 @@ export class DrawHandlerImpl implements DrawHandler {
}); });
this.drawInstance.on('drawdone', (e: CustomEvent): void => { this.drawInstance.on('drawdone', (e: CustomEvent): void => {
const points = translateFromSVG( let points = translateFromSVG(
this.canvas.node as any as SVGSVGElement, this.canvas.node as any as SVGSVGElement,
(e.target as SVGElement) (e.target as SVGElement)
.getAttribute('points') .getAttribute('points')
@ -186,6 +197,11 @@ export class DrawHandlerImpl implements DrawHandler {
.map((coord): number => +coord), .map((coord): number => +coord),
); );
points = translateToSVG(
this.background,
points,
);
const bbox = { const bbox = {
xtl: Number.MAX_SAFE_INTEGER, xtl: Number.MAX_SAFE_INTEGER,
ytl: Number.MAX_SAFE_INTEGER, ytl: Number.MAX_SAFE_INTEGER,
@ -271,9 +287,10 @@ export class DrawHandlerImpl implements DrawHandler {
} }
} }
public constructor(onDrawDone: any, canvas: SVG.Container) { public constructor(onDrawDone: any, canvas: SVG.Container, background: SVGSVGElement) {
this.onDrawDone = onDrawDone; this.onDrawDone = onDrawDone;
this.canvas = canvas; this.canvas = canvas;
this.background = background;
this.drawData = null; this.drawData = null;
this.geometry = null; this.geometry = null;
this.crosshair = null; this.crosshair = null;
@ -317,9 +334,9 @@ export class DrawHandlerImpl implements DrawHandler {
'stroke-width': consts.BASE_STROKE_WIDTH / geometry.scale, 'stroke-width': consts.BASE_STROKE_WIDTH / geometry.scale,
}); });
const PaintHandler = Object.values(this.drawInstance.memory())[0]; const paintHandler = this.drawInstance.remember('_paintHandler');
for (const point of (PaintHandler as any).set.members) { for (const point of (paintHandler as any).set.members) {
point.style( point.style(
'stroke-width', 'stroke-width',
`${consts.BASE_STROKE_WIDTH / (3 * geometry.scale)}`, `${consts.BASE_STROKE_WIDTH / (3 * geometry.scale)}`,

@ -0,0 +1,19 @@
import { GroupData } from './canvasModel';
export interface GroupHandler {
group(groupData: GroupData): void;
}
export class GroupHandlerImpl implements GroupHandler {
// callback is used to notify about grouping end
private onGroupDone: (objects: any[], reset: boolean) => void;
public constructor(onGroupDone: any) {
this.onGroupDone = onGroupDone;
}
/* eslint-disable-next-line */
public group(groupData: GroupData): void {
throw new Error('Method not implemented.');
}
}

@ -0,0 +1,19 @@
import { MergeData } from './canvasModel';
export interface MergeHandler {
merge(mergeData: MergeData): void;
}
export class MergeHandlerImpl implements MergeHandler {
// callback is used to notify about merging end
private onMergeDone: (objects: any[]) => void;
public constructor(onMergeDone: any) {
this.onMergeDone = onMergeDone;
}
/* eslint-disable-next-line */
public merge(mergeData: MergeData): void {
throw new Error('Method not implemented.');
}
}

@ -0,0 +1,19 @@
import { SplitData } from './canvasModel';
export interface SplitHandler {
split(splitData: SplitData): void;
}
export class SplitHandlerImpl implements SplitHandler {
// callback is used to notify about splitting end
private onSplitDone: (object: any) => void;
public constructor(onSplitDone: any) {
this.onSplitDone = onSplitDone;
}
/* eslint-disable-next-line */
public split(splitData: SplitData): void {
throw new Error('Method not implemented.');
}
}

@ -1,5 +1,6 @@
{ {
"compilerOptions": { "compilerOptions": {
"baseUrl": ".",
"emitDeclarationOnly": true, "emitDeclarationOnly": true,
"module": "es6", "module": "es6",
"target": "es6", "target": "es6",
@ -7,9 +8,12 @@
"preserveConstEnums": true, "preserveConstEnums": true,
"declaration": true, "declaration": true,
"moduleResolution": "node", "moduleResolution": "node",
"declarationDir": "dist/declaration" "declarationDir": "dist/declaration",
"paths": {
"cvat-canvas.node": ["dist/cvat-canvas.node"]
}
}, },
"include": [ "include": [
"src/*.ts" "src/*.ts"
], ]
} }

@ -1,11 +1,46 @@
/* global /* eslint-disable */
require:true,
__dirname:true,
*/
const path = require('path'); const path = require('path');
const DtsBundleWebpack = require('dts-bundle-webpack')
module.exports = { const nodeConfig = {
target: 'node',
mode: 'production',
devtool: 'source-map',
entry: './src/canvas.ts',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'cvat-canvas.node.js',
library: 'canvas',
libraryTarget: 'commonjs',
},
resolve: {
extensions: ['.ts', '.js', '.json'],
},
module: {
rules: [{
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: [
['@babel/preset-env'],
['@babel/typescript'],
],
sourceType: 'unambiguous',
},
},
}],
},
plugins: [
new DtsBundleWebpack({
name: 'cvat-canvas.node',
main: 'dist/declaration/canvas.d.ts',
out: '../cvat-canvas.node.d.ts',
}),
]
};
const webConfig = {
target: 'web', target: 'web',
mode: 'production', mode: 'production',
devtool: 'source-map', devtool: 'source-map',
@ -32,16 +67,21 @@ module.exports = {
loader: 'babel-loader', loader: 'babel-loader',
options: { options: {
presets: [ presets: [
[ ['@babel/preset-env'],
'@babel/preset-env', ['@babel/typescript'],
],
[
'@babel/typescript',
],
], ],
sourceType: 'unambiguous', sourceType: 'unambiguous',
}, },
}, },
}], }],
}, },
plugins: [
new DtsBundleWebpack({
name: 'cvat-canvas',
main: 'dist/declaration/canvas.d.ts',
out: '../cvat-canvas.d.ts',
}),
]
}; };
module.exports = [webConfig, nodeConfig]

Loading…
Cancel
Save