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
## Description
The CVAT module 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```
The CVAT module written in TypeScript language.
It presents a canvas to viewing, drawing and editing of annotations.
## Commands
- 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
```
## Creation
Canvas is created by using constructor:
```js
const { Canvas } = require('./canvas');
const canvas = new Canvas(ObjectStateClass);
```
- Canvas has transparent background
## Using
Canvas itself handles:
- Shape context menu (PKM)
@ -39,12 +29,14 @@ Canvas itself handles:
- Remove point (PKM)
- Polyshape editing (Shift + LKM)
## API
### Methods
All methods are sync.
### API Methods
```ts
enum Rotation {
ANTICLOCKWISE90,
CLOCKWISE90,
}
interface DrawData {
enabled: boolean;
shapeType?: string;
@ -53,23 +45,38 @@ All methods are sync.
crosshair?: boolean;
}
html(): HTMLDivElement;
setup(frameData: FrameData, objectStates: ObjectState): 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;
interface GroupData {
enabled: boolean;
resetGroup?: boolean;
}
draw(drawData: DrawData): void;
split(enabled?: boolean): void;
group(enabled?: boolean): void;
merge(enabled?: boolean): void;
interface MergeData {
enabled: boolean;
}
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}```
- Drawn shapes and tracks have classes ```cvat_canvas_shape```,
@ -90,15 +97,72 @@ Standard JS events are used.
- canvas.setup
- canvas.activated => ObjectState
- canvas.deactivated
- canvas.moved => [ObjectState], x, y
- canvas.drawn => ObjectState
- canvas.edited => ObjectState
- canvas.splitted => ObjectState
- canvas.groupped => [ObjectState]
- canvas.merged => [ObjectState]
- canvas.moved => {states: ObjectState[], x: number, y: number}
- canvas.drawn => {state: ObjectState}
- canvas.edited => {state: ObjectState}
- canvas.splitted => {state: ObjectState, frame: number}
- canvas.groupped => {states: ObjectState[], reset: boolean}
- canvas.merged => {states: ObjectState[]}
- 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
![](images/states.svg)

@ -2,7 +2,7 @@
"name": "cvat-canvas",
"version": "0.1.0",
"description": "Part of Computer Vision Annotation Tool which presents its canvas library",
"main": "babel.config.js",
"main": "src/canvas.ts",
"scripts": {
"build": "tsc && webpack --config ./webpack.config.js",
"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/parser": "^1.13.0",
"babel-loader": "^8.0.6",
"dts-bundle-webpack": "^1.0.2",
"eslint": "^6.1.0",
"eslint-config-airbnb-typescript": "^4.0.1",
"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 {
CanvasModel,
CanvasModelImpl,
Rotation,
DrawData,
MergeData,
SplitData,
GroupData,
CanvasModel,
CanvasModelImpl,
} from './canvasModel';
import {
Master,
} from './master';
import {
CanvasController,
CanvasControllerImpl,
@ -20,6 +27,7 @@ import {
CanvasViewImpl,
} from './canvasView';
interface Canvas {
html(): HTMLDivElement;
setup(frameData: any, objectStates: any[]): void;
@ -30,15 +38,15 @@ interface Canvas {
grid(stepX: number, stepY: number): void;
draw(drawData: DrawData): void;
split(enabled?: boolean): void;
group(enabled?: boolean): void;
merge(enabled?: boolean): void;
group(groupData: GroupData): void;
split(splitData: SplitData): void;
merge(mergeData: MergeData): void;
cancel(): void;
}
class CanvasImpl implements Canvas {
private model: CanvasModel;
private model: CanvasModel & Master;
private controller: CanvasController;
private view: CanvasView;
@ -60,7 +68,7 @@ class CanvasImpl implements Canvas {
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);
}
@ -80,16 +88,16 @@ class CanvasImpl implements Canvas {
this.model.draw(drawData);
}
public split(enabled: boolean = false): void {
this.model.split(enabled);
public split(splitData: SplitData): void {
this.model.split(splitData);
}
public group(enabled: boolean = false): void {
this.model.group(enabled);
public group(groupData: GroupData): void {
this.model.group(groupData);
}
public merge(enabled: boolean = false): void {
this.model.merge(enabled);
public merge(mergeData: MergeData): void {
this.model.merge(mergeData);
}
public cancel(): void {
@ -97,6 +105,7 @@ class CanvasImpl implements Canvas {
}
}
export {
CanvasImpl as Canvas,
Rotation,

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

@ -3,9 +3,6 @@
* SPDX-License-Identifier: MIT
*/
// Disable till full implementation
/* eslint class-methods-use-this: "off" */
import * as SVG from 'svg.js';
// tslint:disable-next-line: ordered-imports
@ -16,6 +13,9 @@ import 'svg.select.js';
import { CanvasController } from './canvasController';
import { Listener, Master } from './master';
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 consts from './consts';
import {
@ -49,6 +49,48 @@ enum Mode {
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 {
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));
@ -78,6 +120,9 @@ export class CanvasViewImpl implements CanvasView, Listener {
private svgShapes: ShapeDict;
private svgTexts: TextDict;
private drawHandler: DrawHandler;
private mergeHandler: MergeHandler;
private splitHandler: SplitHandler;
private groupHandler: GroupHandler;
private activeElement: {
state: any;
attributeID: number;
@ -85,7 +130,7 @@ export class CanvasViewImpl implements CanvasView, Listener {
private mode: Mode;
private onDrawDone(data: Record<string, any>): void {
private onDrawDone(data: object): void {
if (data) {
const event: CustomEvent = new CustomEvent('canvas.drawn', {
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) {
this.controller = controller;
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.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');
const loadingCircle: SVGCircleElement = window.document
@ -203,6 +312,24 @@ export class CanvasViewImpl implements CanvasView, Listener {
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 => {
self.controller.fit();
});
@ -239,7 +366,7 @@ export class CanvasViewImpl implements CanvasView, Listener {
detail: {
x,
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 {
this.adoptedContent.clear();
const ctm = this.content.getScreenCTM()
.inverse().multiply(this.background.getScreenCTM());
this.deactivate();
// 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);
// TODO: Update objects
// TODO: Delete objects
this.mergeHandler.updateObjects(objects);
this.groupHandler.updateObjects(objects);
this.splitHandler.updateObjects(objects);
}
const { geometry } = this.controller;
@ -498,7 +642,7 @@ export class CanvasViewImpl implements CanvasView, Listener {
(shape as any).draggable(false);
if (state.shapeType !== 'points') {
this.selectize(false, shape, null);
selectize(false, shape, null);
}
(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 {
// Check if other element have been already activated
if (this.activeElement) {
@ -605,7 +707,7 @@ export class CanvasViewImpl implements CanvasView, Listener {
});
if (state.shapeType !== 'points') {
this.selectize(true, shape, geometry);
selectize(true, shape, geometry);
}
(shape as any).resize().on('resizestart', (): void => {
@ -731,12 +833,17 @@ export class CanvasViewImpl implements CanvasView, Listener {
zOrder: state.zOrder,
}).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');
return shape;
}
/* eslint-disable-next-line */
private addTag(state: any, geometry: Geometry): void {
console.log(state, geometry);
}

@ -23,8 +23,10 @@ export interface 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 background: SVGSVGElement;
private crosshair: {
x: SVG.Line;
y: SVG.Line;
@ -70,8 +72,12 @@ export class DrawHandlerImpl implements DrawHandler {
} else {
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],
);
([xtl, ytl, xbr, ybr] = translateToSVG(
this.background,
[xtl, ytl, xbr, ybr],
));
xtl = Math.min(Math.max(xtl, 0), frameWidth);
xbr = Math.min(Math.max(xbr, 0), frameWidth);
ytl = Math.min(Math.max(ytl, 0), frameHeight);
@ -178,7 +189,7 @@ export class DrawHandlerImpl implements DrawHandler {
});
this.drawInstance.on('drawdone', (e: CustomEvent): void => {
const points = translateFromSVG(
let points = translateFromSVG(
this.canvas.node as any as SVGSVGElement,
(e.target as SVGElement)
.getAttribute('points')
@ -186,6 +197,11 @@ export class DrawHandlerImpl implements DrawHandler {
.map((coord): number => +coord),
);
points = translateToSVG(
this.background,
points,
);
const bbox = {
xtl: 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.canvas = canvas;
this.background = background;
this.drawData = null;
this.geometry = null;
this.crosshair = null;
@ -317,9 +334,9 @@ export class DrawHandlerImpl implements DrawHandler {
'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(
'stroke-width',
`${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": {
"baseUrl": ".",
"emitDeclarationOnly": true,
"module": "es6",
"target": "es6",
@ -7,9 +8,12 @@
"preserveConstEnums": true,
"declaration": true,
"moduleResolution": "node",
"declarationDir": "dist/declaration"
"declarationDir": "dist/declaration",
"paths": {
"cvat-canvas.node": ["dist/cvat-canvas.node"]
}
},
"include": [
"src/*.ts"
],
]
}

@ -1,11 +1,46 @@
/* global
require:true,
__dirname:true,
*/
/* eslint-disable */
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',
mode: 'production',
devtool: 'source-map',
@ -32,16 +67,21 @@ module.exports = {
loader: 'babel-loader',
options: {
presets: [
[
'@babel/preset-env',
],
[
'@babel/typescript',
],
['@babel/preset-env'],
['@babel/typescript'],
],
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