CVAT-Canvas: Integrated SVG.js (#629)

* Canvas rotation

* Integrated SVG.js. Drawing, dragging and resizing of shapes

* Removed TODO list
main
Boris Sekachev 7 years ago committed by Nikita Manovich
parent 7467e298fd
commit 58f30220df

@ -14,11 +14,6 @@ npm run build
npm run build -- --mode=development # without a minification npm run build -- --mode=development # without a minification
``` ```
- Running development server
```bash
npm run server
```
- Updating of a module version: - Updating of a module version:
```bash ```bash
npm version patch # updated after minor fixes npm version patch # updated after minor fixes
@ -53,7 +48,7 @@ All methods are sync.
html(): HTMLDivElement; html(): HTMLDivElement;
setup(frameData: FrameData, objectStates: ObjectState): void; setup(frameData: FrameData, objectStates: ObjectState): void;
activate(clientID: number, attributeID?: number): void; activate(clientID: number, attributeID?: number): void;
rotate(direction: Rotation): void; rotate(rotation: Rotation, remember?: boolean): void;
focus(clientID: number, padding?: number): void; focus(clientID: number, padding?: number): void;
fit(): void; fit(): void;
grid(stepX: number, stepY: number): void; grid(stepX: number, stepY: number): void;

@ -2,6 +2,37 @@
display: none; display: none;
} }
.cvat_canvas_shape {
fill-opacity: 0.1;
stroke-opacity: 1;
}
polyline.cvat_canvas_shape {
fill-opacity: 0;
stroke-opacity: 1;
}
.cvat_canvas_shape_activated {
}
.cvat_canvas_shape_grouping {
}
.cvat_canvas_shape_merging {
}
.cvat_canvas_shape_drawing {
}
.svg_select_boundingRect {
opacity: 0;
pointer-events: none;
}
#cvat_canvas_wrapper { #cvat_canvas_wrapper {
width: 100%; width: 100%;
height: 80%; height: 80%;
@ -41,6 +72,7 @@
pointer-events: none; pointer-events: none;
width: 100%; width: 100%;
height: 100%; height: 100%;
pointer-events: none;
} }
#cvat_canvas_background { #cvat_canvas_background {
@ -59,6 +91,7 @@
pointer-events: none; pointer-events: none;
width: 100%; width: 100%;
height: 100%; height: 100%;
pointer-events: none;
} }
#cvat_canvas_grid_pattern { #cvat_canvas_grid_pattern {

@ -10,7 +10,10 @@
"author": "Intel", "author": "Intel",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@svgdotjs/svg.js": "^3.0.13" "svg.draggable.js": "^2.2.2",
"svg.js": "^2.7.1",
"svg.resize.js": "^1.4.3",
"svg.select.js": "^3.0.1"
}, },
"devDependencies": { "devDependencies": {
"@babel/cli": "^7.5.5", "@babel/cli": "^7.5.5",

@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2018 Intel Corporation * Copyright (C) 2019 Intel Corporation
* SPDX-License-Identifier: MIT * SPDX-License-Identifier: MIT
*/ */
@ -11,7 +11,7 @@ interface Canvas {
html(): HTMLDivElement; html(): HTMLDivElement;
setup(frameData: any, objectStates: any[]): void; setup(frameData: any, objectStates: any[]): void;
activate(clientID: number, attributeID?: number): void; activate(clientID: number, attributeID?: number): void;
rotate(direction: Rotation): void; rotate(rotation: Rotation, remember?: boolean): void;
focus(clientID: number, padding?: number): void; focus(clientID: number, padding?: number): void;
fit(): void; fit(): void;
grid(stepX: number, stepY: number): void; grid(stepX: number, stepY: number): void;
@ -47,8 +47,8 @@ class CanvasImpl implements Canvas {
this.model.activate(clientID, attributeID); this.model.activate(clientID, attributeID);
} }
public rotate(direction: Rotation): void { public rotate(rotation: Rotation, remember: boolean): void {
this.model.rotate(direction); this.model.rotate(rotation, remember);
} }
public focus(clientID: number, padding: number = 0): void { public focus(clientID: number, padding: number = 0): void {

@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2018 Intel Corporation * Copyright (C) 2019 Intel Corporation
* SPDX-License-Identifier: MIT * SPDX-License-Identifier: MIT
*/ */
@ -12,6 +12,7 @@ import {
export interface CanvasController { export interface CanvasController {
readonly geometry: Geometry; readonly geometry: Geometry;
readonly objects: any[];
canvasSize: Size; canvasSize: Size;
zoom(x: number, y: number, direction: number): void; zoom(x: number, y: number, direction: number): void;
@ -31,10 +32,6 @@ export class CanvasControllerImpl implements CanvasController {
this.model = model; this.model = model;
} }
public get geometry(): Geometry {
return this.model.geometry;
}
public zoom(x: number, y: number, direction: number): void { public zoom(x: number, y: number, direction: number): void {
this.model.zoom(x, y, direction); this.model.zoom(x, y, direction);
} }
@ -43,14 +40,6 @@ export class CanvasControllerImpl implements CanvasController {
this.model.fit(); this.model.fit();
} }
public set canvasSize(value: Size) {
this.model.canvasSize = value;
}
public get canvasSize(): Size {
return this.model.canvasSize;
}
public enableDrag(x: number, y: number): void { public enableDrag(x: number, y: number): void {
this.lastDragPosition = { this.lastDragPosition = {
x, x,
@ -74,4 +63,20 @@ export class CanvasControllerImpl implements CanvasController {
public disableDrag(): void { public disableDrag(): void {
this.isDragging = false; this.isDragging = false;
} }
public get geometry(): Geometry {
return this.model.geometry;
}
public get objects(): any[] {
return this.model.objects;
}
public set canvasSize(value: Size) {
this.model.canvasSize = value;
}
public get canvasSize(): Size {
return this.model.canvasSize;
}
} }

@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2018 Intel Corporation * Copyright (C) 2019 Intel Corporation
* SPDX-License-Identifier: MIT * SPDX-License-Identifier: MIT
*/ */
@ -22,6 +22,7 @@ export interface Geometry {
left: number; left: number;
scale: number; scale: number;
offset: number; offset: number;
angle: number;
} }
export enum FrameZoom { export enum FrameZoom {
@ -30,19 +31,21 @@ export enum FrameZoom {
} }
export enum Rotation { export enum Rotation {
CLOCKWISE90,
ANTICLOCKWISE90, ANTICLOCKWISE90,
CLOCKWISE90,
} }
export enum UpdateReasons { export enum UpdateReasons {
IMAGE = 'image', IMAGE = 'image',
OBJECTS = 'objects',
ZOOM = 'zoom', ZOOM = 'zoom',
FIT = 'fit', FIT = 'fit',
MOVE = 'move', MOVE = 'move',
} }
export interface CanvasModel extends MasterImpl { export interface CanvasModel extends MasterImpl {
image: string; readonly image: string;
readonly objects: any[];
geometry: Geometry; geometry: Geometry;
imageSize: Size; imageSize: Size;
canvasSize: Size; canvasSize: Size;
@ -52,7 +55,7 @@ export interface CanvasModel extends MasterImpl {
setup(frameData: any, objectStates: any[]): void; setup(frameData: any, objectStates: any[]): void;
activate(clientID: number, attributeID: number): void; activate(clientID: number, attributeID: number): void;
rotate(direction: Rotation): void; rotate(rotation: Rotation, remember: boolean): void;
focus(clientID: number, padding: number): void; focus(clientID: number, padding: number): void;
fit(): void; fit(): void;
grid(stepX: number, stepY: number): void; grid(stepX: number, stepY: number): void;
@ -68,18 +71,22 @@ export interface CanvasModel extends MasterImpl {
export class CanvasModelImpl extends MasterImpl implements CanvasModel { export class CanvasModelImpl extends MasterImpl implements CanvasModel {
private data: { private data: {
image: string; image: string;
objects: any[];
imageSize: Size; imageSize: Size;
canvasSize: Size; canvasSize: Size;
imageOffset: number; imageOffset: number;
scale: number; scale: number;
top: number; top: number;
left: number; left: number;
angle: number;
rememberAngle: boolean;
}; };
public constructor() { public constructor() {
super(); super();
this.data = { this.data = {
angle: 0,
canvasSize: { canvasSize: {
height: 0, height: 0,
width: 0, width: 0,
@ -91,6 +98,8 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel {
width: 0, width: 0,
}, },
left: 0, left: 0,
objects: [],
rememberAngle: false,
scale: 1, scale: 1,
top: 0, top: 0,
}; };
@ -124,8 +133,14 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel {
width: (frameData.width as number), width: (frameData.width as number),
}; };
if (!this.data.rememberAngle) {
this.data.angle = 0;
}
this.data.image = data; this.data.image = data;
this.notify(UpdateReasons.IMAGE); this.notify(UpdateReasons.IMAGE);
this.data.objects = objectStates;
this.notify(UpdateReasons.OBJECTS);
}).catch((exception: any): void => { }).catch((exception: any): void => {
console.log(exception.toString()); console.log(exception.toString());
}); });
@ -137,8 +152,16 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel {
console.log(clientID, attributeID); console.log(clientID, attributeID);
} }
public rotate(direction: Rotation): void { public rotate(rotation: Rotation, remember: boolean = false): void {
console.log(direction); if (rotation === Rotation.CLOCKWISE90) {
this.data.angle += 90;
} else {
this.data.angle -= 90;
}
this.data.angle %= 360;
this.data.rememberAngle = remember;
this.fit();
} }
public focus(clientID: number, padding: number): void { public focus(clientID: number, padding: number): void {
@ -146,10 +169,20 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel {
} }
public fit(): void { public fit(): void {
this.data.scale = Math.min( const { angle } = this.data;
this.data.canvasSize.width / this.data.imageSize.width,
this.data.canvasSize.height / this.data.imageSize.height, if ((angle / 90) % 2) {
); // 90, 270, ..
this.data.scale = Math.min(
this.data.canvasSize.width / this.data.imageSize.height,
this.data.canvasSize.height / this.data.imageSize.width,
);
} else {
this.data.scale = Math.min(
this.data.canvasSize.width / this.data.imageSize.width,
this.data.canvasSize.height / this.data.imageSize.height,
);
}
this.data.scale = Math.min( this.data.scale = Math.min(
Math.max(this.data.scale, FrameZoom.MIN), Math.max(this.data.scale, FrameZoom.MIN),
@ -196,6 +229,7 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel {
public get geometry(): Geometry { public get geometry(): Geometry {
return { return {
angle: this.data.angle,
canvas: { canvas: {
height: this.data.canvasSize.height, height: this.data.canvasSize.height,
width: this.data.canvasSize.width, width: this.data.canvasSize.width,
@ -215,6 +249,10 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel {
return this.data.image; return this.data.image;
} }
public get objects(): any[] {
return this.data.objects;
}
public set imageSize(value: Size) { public set imageSize(value: Size) {
this.data.imageSize = { this.data.imageSize = {
height: value.height, height: value.height,

@ -1,8 +1,15 @@
/* /*
* Copyright (C) 2018 Intel Corporation * Copyright (C) 2019 Intel Corporation
* SPDX-License-Identifier: MIT * SPDX-License-Identifier: MIT
*/ */
import * as SVG from 'svg.js';
// tslint:disable-next-line: ordered-imports
import 'svg.draggable.js';
import 'svg.resize.js';
import 'svg.select.js';
import { CanvasController } from './canvasController'; import { CanvasController } from './canvasController';
import { CanvasModel, Geometry, UpdateReasons } from './canvasModel'; import { CanvasModel, Geometry, UpdateReasons } from './canvasModel';
import { Listener, Master } from './master'; import { Listener, Master } from './master';
@ -11,10 +18,6 @@ export interface CanvasView {
html(): HTMLDivElement; html(): HTMLDivElement;
} }
interface HTMLAttribute {
[index: string]: string;
}
function translateToSVG(svg: SVGSVGElement, points: number[]): number[] { function translateToSVG(svg: SVGSVGElement, points: number[]): number[] {
const output = []; const output = [];
const transformationMatrix = svg.getScreenCTM().inverse(); const transformationMatrix = svg.getScreenCTM().inverse();
@ -29,44 +32,56 @@ function translateToSVG(svg: SVGSVGElement, points: number[]): number[] {
return output; return output;
} }
function translateFromSVG(svg: SVGSVGElement, points: number[]): number[] { function darker(color: string, percentage: number) {
const output = []; const R = Math.round(parseInt(color.slice(1, 3), 16) * (1 - percentage / 100));
const transformationMatrix = svg.getScreenCTM(); const G = Math.round(parseInt(color.slice(3, 5), 16) * (1 - percentage / 100));
let pt = svg.createSVGPoint(); const B = Math.round(parseInt(color.slice(5, 7), 16) * (1 - percentage / 100));
for (let i = 0; i < points.length; i += 2) {
[pt.x] = points;
[, pt.y] = points;
pt = pt.matrixTransform(transformationMatrix);
output.push(pt.x, pt.y);
}
return output; const rHex = Math.max(0, R).toString(16);
const gHex = Math.max(0, G).toString(16);
const bHex = Math.max(0, B).toString(16);
return `#${rHex.length === 1 ? `0${rHex}` : rHex}`
+ `${gHex.length === 1 ? `0${gHex}` : gHex}`
+ `${bHex.length === 1 ? `0${bHex}` : bHex}`;
} }
export class CanvasViewImpl implements CanvasView, Listener { export class CanvasViewImpl implements CanvasView, Listener {
private loadingAnimation: SVGSVGElement; private loadingAnimation: SVGSVGElement;
private text: SVGSVGElement; private text: SVGSVGElement;
private adoptedText: SVG.Container;
private background: SVGSVGElement; private background: SVGSVGElement;
private grid: SVGSVGElement; private grid: SVGSVGElement;
private content: SVGSVGElement; private content: SVGSVGElement;
private adoptedContent: SVG.Container;
private rotationWrapper: HTMLDivElement; private rotationWrapper: HTMLDivElement;
private canvas: HTMLDivElement; private canvas: HTMLDivElement;
private gridPath: SVGPathElement; private gridPath: SVGPathElement;
private controller: CanvasController; private controller: CanvasController;
private svgShapes: SVG.Shape[];
private svgTexts: SVG.Text[];
private readonly BASE_STROKE_WIDTH: number;
private readonly BASE_POINT_SIZE: number;
public constructor(model: CanvasModel & Master, controller: CanvasController) { public constructor(model: CanvasModel & Master, controller: CanvasController) {
this.controller = controller; this.controller = controller;
this.BASE_STROKE_WIDTH = 2.5;
this.BASE_POINT_SIZE = 7;
this.svgShapes = [];
this.svgTexts = [];
// Create HTML elements // Create HTML elements
this.loadingAnimation = window.document this.loadingAnimation = window.document
.createElementNS('http://www.w3.org/2000/svg', 'svg'); .createElementNS('http://www.w3.org/2000/svg', 'svg');
this.text = window.document.createElementNS('http://www.w3.org/2000/svg', 'svg'); this.text = window.document.createElementNS('http://www.w3.org/2000/svg', 'svg');
this.adoptedText = (SVG.adopt((this.text as any as HTMLElement)) as SVG.Container);
this.background = window.document.createElementNS('http://www.w3.org/2000/svg', 'svg'); this.background = window.document.createElementNS('http://www.w3.org/2000/svg', 'svg');
this.grid = window.document.createElementNS('http://www.w3.org/2000/svg', 'svg'); this.grid = window.document.createElementNS('http://www.w3.org/2000/svg', 'svg');
this.gridPath = window.document.createElementNS('http://www.w3.org/2000/svg', 'path'); this.gridPath = window.document.createElementNS('http://www.w3.org/2000/svg', 'path');
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.rotationWrapper = window.document.createElement('div'); this.rotationWrapper = window.document.createElement('div');
this.canvas = window.document.createElement('div'); this.canvas = window.document.createElement('div');
@ -167,9 +182,40 @@ export class CanvasViewImpl implements CanvasView, Listener {
public notify(model: CanvasModel & Master, reason: UpdateReasons): void { public notify(model: CanvasModel & Master, reason: UpdateReasons): void {
function transform(geometry: Geometry): void { function transform(geometry: Geometry): void {
// Transform canvas
for (const obj of [this.background, this.grid, this.loadingAnimation, this.content]) { for (const obj of [this.background, this.grid, this.loadingAnimation, this.content]) {
obj.style.transform = `scale(${geometry.scale})`; obj.style.transform = `scale(${geometry.scale})`;
} }
this.rotationWrapper.style.transform = `rotate(${geometry.angle}deg)`;
// Transform all shapes
for (const element of window.document.getElementsByClassName('svg_select_points')) {
element.setAttribute(
'stroke-width',
`${this.BASE_STROKE_WIDTH / (3 * geometry.scale)}`,
);
element.setAttribute(
'r',
`${this.BASE_POINT_SIZE / (2 * geometry.scale)}`,
);
}
for (const element of
window.document.getElementsByClassName('cvat_canvas_selected_point')) {
element.setAttribute(
'stroke-width',
`${+element.getAttribute('stroke-width') * 2}`,
);
}
for (const object of this.svgShapes) {
if (object.attr('stroke-width')) {
object.attr({
'stroke-width': this.BASE_STROKE_WIDTH / (geometry.scale),
});
}
}
} }
function resize(geometry: Geometry): void { function resize(geometry: Geometry): void {
@ -198,6 +244,17 @@ export class CanvasViewImpl implements CanvasView, Listener {
this.content.style.transform = `scale(${geometry.scale})`; this.content.style.transform = `scale(${geometry.scale})`;
} }
function setupObjects(objects: any[], geometry: Geometry): void {
this.adoptedContent.clear();
const ctm = this.content.getScreenCTM()
.inverse().multiply(this.background.getScreenCTM());
// TODO: Compute difference
this.addObjects(ctm, objects, geometry);
// TODO: Update objects
// TODO: Delete objects
}
const { geometry } = this.controller; const { geometry } = this.controller;
if (reason === UpdateReasons.IMAGE) { if (reason === UpdateReasons.IMAGE) {
if (!model.image.length) { if (!model.image.length) {
@ -217,10 +274,162 @@ export class CanvasViewImpl implements CanvasView, Listener {
transform.call(this, geometry); transform.call(this, geometry);
} else if (reason === UpdateReasons.MOVE) { } else if (reason === UpdateReasons.MOVE) {
move.call(this, geometry); move.call(this, geometry);
} else if (reason === UpdateReasons.OBJECTS) {
setupObjects.call(this, this.controller.objects, geometry);
} }
} }
public html(): HTMLDivElement { public html(): HTMLDivElement {
return this.canvas; return this.canvas;
} }
private addObjects(ctm: SVGMatrix, objects: any[], geometry: Geometry) {
for (const object of objects) {
if (object.objectType === 'tag') {
this.addTag(object, geometry);
} else {
const points: number[] = (object.points as number[]);
const translatedPoints: number[] = [];
for (let i = 0; i <= points.length - 1; i += 2) {
let point: SVGPoint = this.background.createSVGPoint();
point.x = points[i];
point.y = points[i + 1];
point = point.matrixTransform(ctm);
translatedPoints.push(point.x, point.y);
}
// TODO: Use enums after typification cvat-core
if (object.shapeType === 'rectangle') {
this.svgShapes.push(this.addRect(translatedPoints, object, geometry));
} else {
const stringified = translatedPoints.reduce(
(acc: string, val: number, idx: number): string => {
if (idx % 2) {
return `${acc}${val} `;
}
return `${acc}${val},`;
},
'' ,
);
if (object.shapeType === 'polygon') {
this.svgShapes.push(this.addPolygon(stringified, object, geometry));
} else if (object.shapeType === 'polyline') {
this.svgShapes.push(this.addPolyline(stringified, object, geometry));
} else if (object.shapeType === 'points') {
this.svgShapes.push(this.addPoints(stringified, object, geometry));
}
}
// TODO: add text here if need
}
}
this.activate(geometry);
}
private activate(geometry: Geometry) {
for (const shape of this.svgShapes) {
const self = this;
(shape as any).draggable().on('dragstart', () => {
console.log('hello');
}).on('dragend', () => {
console.log('hello');
});
(shape as any).selectize({
deepSelect: true,
pointSize: this.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': self.BASE_STROKE_WIDTH / (3 * geometry.scale),
});
circle.node.addEventListener('mouseenter', () => {
circle.attr({
'stroke-width': circle.attr('stroke-width') * 2,
});
circle.addClass('cvat_canvas_selected_point');
});
circle.node.addEventListener('mouseleave', () => {
circle.attr({
'stroke-width': circle.attr('stroke-width') / 2,
});
circle.removeClass('cvat_canvas_selected_point');
});
return circle;
},
}).resize();
}
// add selectable
// add draggable
// add resizable
}
private addRect(points: number[], state: any, geometry: Geometry): SVG.Rect {
const [xtl, ytl, xbr, ybr] = points;
return this.adoptedContent.rect().size(xbr - xtl, ybr - ytl).attr({
client_id: state.clientID,
'color-rendering': 'optimizeQuality',
fill: state.color,
'shape-rendering': 'geometricprecision',
stroke: darker(state.color, 50),
'stroke-width': this.BASE_STROKE_WIDTH / geometry.scale,
z_order: state.zOrder,
}).move(xtl, ytl).addClass('cvat_canvas_shape');
}
private addPolygon(points: string, state: any, geometry: Geometry): SVG.Polygon {
return this.adoptedContent.polygon(points).attr({
client_id: state.clientID,
'color-rendering': 'optimizeQuality',
fill: state.color,
'shape-rendering': 'geometricprecision',
stroke: darker(state.color, 50),
'stroke-width': this.BASE_STROKE_WIDTH / geometry.scale,
z_order: state.zOrder,
}).addClass('cvat_canvas_shape');
}
private addPolyline(points: string, state: any, geometry: Geometry): SVG.PolyLine {
return this.adoptedContent.polyline(points).attr({
client_id: state.clientID,
'color-rendering': 'optimizeQuality',
fill: state.color,
'shape-rendering': 'geometricprecision',
stroke: darker(state.color, 50),
'stroke-width': this.BASE_STROKE_WIDTH / geometry.scale,
z_order: state.zOrder,
}).addClass('cvat_canvas_shape');
}
private addPoints(points: string, state: any, geometry: Geometry): SVG.Polygon {
return this.adoptedContent.polygon(points).attr({
client_id: state.clientID,
'color-rendering': 'optimizeQuality',
fill: state.color,
opacity: 0,
'shape-rendering': 'geometricprecision',
stroke: darker(state.color, 50),
'stroke-width': this.BASE_STROKE_WIDTH / geometry.scale,
z_order: state.zOrder,
}).addClass('cvat_canvas_shape');
}
private addTag(state: any, geometry: Geometry): void {
// TODO:
}
} }

@ -1,3 +1,8 @@
/*
* Copyright (C) 2019 Intel Corporation
* SPDX-License-Identifier: MIT
*/
export interface Master { export interface Master {
subscribe(listener: Listener): void; subscribe(listener: Listener): void;
unsubscribe(listener: Listener): void; unsubscribe(listener: Listener): void;

@ -6,6 +6,7 @@
"noImplicitAny": true, "noImplicitAny": true,
"preserveConstEnums": true, "preserveConstEnums": true,
"declaration": true, "declaration": true,
"moduleResolution": "node",
"declarationDir": "dist/declaration" "declarationDir": "dist/declaration"
}, },
"include": [ "include": [

@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2018 Intel Corporation * Copyright (C) 2019 Intel Corporation
* SPDX-License-Identifier: MIT * SPDX-License-Identifier: MIT
*/ */

@ -749,7 +749,7 @@
return Object.assign({}, this.interpolatePosition( return Object.assign({}, this.interpolatePosition(
leftPosition, leftPosition,
rightPosition, rightPosition,
targetFrame, (targetFrame - leftFrame) / (rightFrame - leftFrame),
), { ), {
keyframe: false, keyframe: false,
}); });
@ -1068,9 +1068,7 @@
} }
} }
interpolatePosition(leftPosition, rightPosition, targetFrame) { interpolatePosition(leftPosition, rightPosition, offset) {
const offset = (targetFrame - leftPosition.frame) / (
rightPosition.frame - leftPosition.frame);
const positionOffset = [ const positionOffset = [
rightPosition.points[0] - leftPosition.points[0], rightPosition.points[0] - leftPosition.points[0],
rightPosition.points[1] - leftPosition.points[1], rightPosition.points[1] - leftPosition.points[1],
@ -1097,7 +1095,7 @@
super(data, clientID, color, injection); super(data, clientID, color, injection);
} }
interpolatePosition(leftPosition, rightPosition, targetFrame) { interpolatePosition(leftPosition, rightPosition, offset) {
function findBox(points) { function findBox(points) {
let xmin = Number.MAX_SAFE_INTEGER; let xmin = Number.MAX_SAFE_INTEGER;
let ymin = Number.MAX_SAFE_INTEGER; let ymin = Number.MAX_SAFE_INTEGER;
@ -1356,9 +1354,6 @@
const absoluteLeftPoints = denormalize(toArray(newLeftPoints), leftBox); const absoluteLeftPoints = denormalize(toArray(newLeftPoints), leftBox);
const absoluteRightPoints = denormalize(toArray(newRightPoints), rightBox); const absoluteRightPoints = denormalize(toArray(newRightPoints), rightBox);
const offset = (targetFrame - leftPosition.frame) / (
rightPosition.frame - leftPosition.frame);
const interpolation = []; const interpolation = [];
for (let i = 0; i < absoluteLeftPoints.length; i++) { for (let i = 0; i < absoluteLeftPoints.length; i++) {
interpolation.push(absoluteLeftPoints[i] + ( interpolation.push(absoluteLeftPoints[i] + (

Loading…
Cancel
Save