提交 d4ce6216 编写于 作者: B Boris Sekachev 提交者: Nikita Manovich

Fix context menu, text visibility for small images (#202)

* Fixed: both context menu are opened simultaneously
* Fixed: shape can be unavailable behind text
* Fixed: invisible text outside frame
上级 ce06c957
......@@ -822,7 +822,7 @@ class _AnnotationForJob(_Annotation):
start_frame=db_path.frame,
stop_frame= self.stop_frame,
group_id=db_path.group_id,
client_id=db_path.id,
client_id=db_path.client_id,
)
for db_attr in db_path.attributes:
spec = self.db_attributes[db_attr.spec_id]
......
......@@ -4,7 +4,7 @@
* SPDX-License-Identifier: MIT
*/
/* exported callAnnotationUI translateSVGPos blurAllElements drawBoxSize copyToClipboard */
/* exported callAnnotationUI blurAllElements drawBoxSize copyToClipboard */
"use strict";
function callAnnotationUI(jid) {
......@@ -40,6 +40,7 @@ function buildAnnotationUI(job, shapeData, loadJobEvent) {
// Setup some API
window.cvat = {
labelsInfo: new LabelsInfo(job),
translate: new CoordinateTranslator(),
player: {
geometry: {
scale: 1,
......@@ -53,7 +54,8 @@ function buildAnnotationUI(job, shapeData, loadJobEvent) {
mode: null,
job: {
z_order: job.z_order,
id: job.jobid
id: job.jobid,
images: job.image_meta_data,
},
search: {
value: window.location.search,
......@@ -140,6 +142,8 @@ function buildAnnotationUI(job, shapeData, loadJobEvent) {
let aamModel = new AAMModel(shapeCollectionModel, (xtl, xbr, ytl, ybr) => {
playerModel.focus(xtl, xbr, ytl, ybr);
}, () => {
playerModel.fit();
});
let aamController = new AAMController(aamModel);
new AAMView(aamModel, aamController);
......@@ -730,25 +734,6 @@ function saveAnnotation(shapeCollectionModel, job) {
});
}
function translateSVGPos(svgCanvas, clientX, clientY) {
let pt = svgCanvas.createSVGPoint();
pt.x = clientX;
pt.y = clientY;
pt = pt.matrixTransform(svgCanvas.getScreenCTM().inverse());
let pos = {
x: pt.x,
y: pt.y
};
if (platform.name.toLowerCase() == 'firefox') {
pos.x /= window.cvat.player.geometry.scale;
pos.y /= window.cvat.player.geometry.scale;
}
return pos;
}
function blurAllElements() {
document.activeElement.blur();
......
......@@ -10,10 +10,11 @@
const AAMUndefinedKeyword = '__undefined__';
class AAMModel extends Listener {
constructor(shapeCollection, focus) {
constructor(shapeCollection, focus, fit) {
super('onAAMUpdate', () => this);
this._shapeCollection = shapeCollection;
this._focus = focus;
this._fit = fit;
this._activeAAM = false;
this._activeIdx = null;
this._active = null;
......@@ -167,6 +168,7 @@ class AAMModel extends Listener {
// Notify for remove aam UI
this.notify();
this._fit();
}
}
......
/*
* Copyright (C) 2018 Intel Corporation
*
* SPDX-License-Identifier: MIT
*/
/* exported CoordinateTranslator */
"use strict";
class CoordinateTranslator {
constructor() {
this._boxTranslator = {
_playerOffset: 0,
_convert: function(box, sign) {
for (let prop of ["xtl", "ytl", "xbr", "ybr", "x", "y"]) {
if (prop in box) {
box[prop] += this._playerOffset * sign;
}
}
return box;
},
actualToCanvas: function(actualBox) {
let canvasBox = {};
for (let key in actualBox) {
canvasBox[key] = actualBox[key];
}
return this._convert(canvasBox, 1);
},
canvasToActual: function(canvasBox) {
let actualBox = {};
for (let key in canvasBox) {
actualBox[key] = canvasBox[key];
}
return this._convert(actualBox, -1);
},
};
this._pointsTranslator = {
_playerOffset: 0,
_convert: function(points, sign) {
if (typeof(points) === 'string') {
return points.split(' ').map((coord) => coord.split(',')
.map((x) => +x + this._playerOffset * sign).join(',')).join(' ');
}
else if (typeof(points) === 'object') {
let result = [];
for (let point of points) {
result.push({
x: point.x + this._playerOffset * sign,
y: point.y + this._playerOffset * sign,
});
}
return result;
}
else {
throw Error('Unknown points type was found');
}
},
actualToCanvas: function(actualPoints) {
return this._convert(actualPoints, 1);
},
canvasToActual: function(canvasPoints) {
return this._convert(canvasPoints, -1);
}
},
this._pointTranslator = {
clientToCanvas: function(targetCanvas, clientX, clientY) {
let pt = targetCanvas.createSVGPoint();
pt.x = clientX;
pt.y = clientY;
pt = pt.matrixTransform(targetCanvas.getScreenCTM().inverse());
return pt;
},
canvasToClient: function(sourceCanvas, canvasX, canvasY) {
let pt = sourceCanvas.createSVGPoint();
pt.x = canvasX;
pt.y = canvasY;
pt = pt.matrixTransform(sourceCanvas.getScreenCTM());
return pt;
}
};
}
get box() {
return this._boxTranslator;
}
get points() {
return this._pointsTranslator;
}
get point() {
return this._pointTranslator;
}
set playerOffset(value) {
this._boxTranslator._playerOffset = value;
this._pointsTranslator._playerOffset = value;
}
}
......@@ -152,8 +152,15 @@ class PlayerModel extends Listener {
top: 0,
width: playerSize.width,
height: playerSize.height,
frameOffset: 0,
};
this._geometry.frameOffset = Math.floor(Math.max(
(playerSize.height - MIN_PLAYER_SCALE) / MIN_PLAYER_SCALE,
(playerSize.width - MIN_PLAYER_SCALE) / MIN_PLAYER_SCALE
));
window.cvat.translate.playerOffset = this._geometry.frameOffset;
this._frameProvider.subscribe(this);
}
......@@ -167,11 +174,7 @@ class PlayerModel extends Listener {
}
get geometry() {
return {
scale: this._geometry.scale,
top: this._geometry.top,
left: this._geometry.left
};
return Object.assign({}, this._geometry);
}
get playing() {
......@@ -498,7 +501,6 @@ class PlayerController {
this._moving = true;
this._lastClickX = e.clientX;
this._lastClickY = e.clientY;
e.preventDefault();
}
}
......@@ -520,6 +522,7 @@ class PlayerController {
let leftOffset = e.clientX - this._lastClickX;
this._lastClickX = e.clientX;
this._lastClickY = e.clientY;
this._model.move(topOffset, leftOffset);
}
}
......@@ -649,10 +652,19 @@ class PlayerView {
this._playerGridPath = $('#playerGridPath');
this._contextMenuUI = $('#playerContextMenu');
$('*').on('mouseup', () => this._controller.frameMouseUp());
$('*').on('mouseup.player', () => this._controller.frameMouseUp());
this._playerContentUI.on('mousedown', (e) => {
let pos = window.cvat.translate.point.clientToCanvas(this._playerBackgroundUI[0], e.clientX, e.clientY);
let frameWidth = window.cvat.player.geometry.frameWidth;
let frameHeight = window.cvat.player.geometry.frameHeight;
if (pos.x >= 0 && pos.y >= 0 && pos.x <= frameWidth && pos.y <= frameHeight) {
this._controller.frameMouseDown(e);
}
e.preventDefault();
});
this._playerUI.on('wheel', (e) => this._controller.zoom(e));
this._playerUI.on('dblclick', () => this._controller.fit());
this._playerContentUI.on('mousedown', (e) => this._controller.frameMouseDown(e));
this._playerUI.on('mousemove', (e) => this._controller.frameMouseMove(e));
this._progressUI.on('mousedown', (e) => this._controller.progressMouseDown(e));
this._progressUI.on('mouseup', () => this._controller.progressMouseUp());
......@@ -852,7 +864,7 @@ class PlayerView {
this._progressUI['0'].value = frames.current - frames.start;
for (let obj of [this._playerBackgroundUI, this._playerContentUI, this._playerGridUI]) {
for (let obj of [this._playerBackgroundUI, this._playerGridUI]) {
obj.css('width', image.width);
obj.css('height', image.height);
obj.css('top', geometry.top);
......@@ -860,6 +872,12 @@ class PlayerView {
obj.css('transform', 'scale(' + geometry.scale + ')');
}
this._playerContentUI.css('width', image.width + geometry.frameOffset * 2);
this._playerContentUI.css('height', image.height + geometry.frameOffset * 2);
this._playerContentUI.css('top', geometry.top - geometry.frameOffset * geometry.scale);
this._playerContentUI.css('left', geometry.left - geometry.frameOffset * geometry.scale);
this._playerContentUI.css('transform', 'scale(' + geometry.scale + ')');
this._playerGridPath.attr('stroke-width', 2 / geometry.scale);
this._frameNumber.prop('value', frames.current);
}
......
......@@ -50,7 +50,7 @@ class ShapeBufferModel extends Listener {
}
}
_makeObject(bbRect, polyPoints, trackedObj) {
_makeObject(box, points, trackedObj) {
if (!this._shape.type) {
return null;
}
......@@ -71,17 +71,10 @@ class ShapeBufferModel extends Listener {
object.attributes = attributes;
if (this._shape.type === 'box') {
let box = {};
box.xtl = Math.max(bbRect.x, 0);
box.ytl = Math.max(bbRect.y, 0);
box.xbr = Math.min(bbRect.x + bbRect.width, window.cvat.player.geometry.frameWidth);
box.ybr = Math.min(bbRect.y + bbRect.height, window.cvat.player.geometry.frameHeight);
box.occluded = this._shape.position.occluded;
box.frame = window.cvat.player.frames.current;
box.z_order = this._collection.zOrder(box.frame).max;
if (trackedObj) {
object.shapes = [];
object.shapes.push(Object.assign(box, {
......@@ -95,8 +88,7 @@ class ShapeBufferModel extends Listener {
}
else {
let position = {};
position.points = polyPoints;
position.points = points;
position.occluded = this._shape.position.occluded;
position.frame = window.cvat.player.frames.current;
position.z_order = this._collection.zOrder(position.frame).max;
......@@ -135,78 +127,91 @@ class ShapeBufferModel extends Listener {
return false;
}
pasteToFrame(bbRect, polyPoints) {
if (!this._shape.type) {
return;
}
pasteToFrame(box, polyPoints) {
let object = this._makeObject(box, polyPoints, this._shape.mode === 'interpolation');
Logger.addEvent(Logger.EventType.pasteObject);
let object = this._makeObject(bbRect, polyPoints, this._shape.mode === 'interpolation');
if (this._shape.type === 'box') {
this._collection.add(object, `${this._shape.mode}_${this._shape.type}`);
}
else {
this._collection.add(object, `annotation_${this._shape.type}`);
}
if (object) {
Logger.addEvent(Logger.EventType.pasteObject);
if (this._shape.type === 'box') {
this._collection.add(object, `${this._shape.mode}_${this._shape.type}`);
}
else {
this._collection.add(object, `annotation_${this._shape.type}`);
}
// Undo/redo code
let model = this._collection.shapes.slice(-1)[0];
window.cvat.addAction('Paste Object', () => {
model.removed = true;
model.unsubscribe(this._collection);
}, () => {
model.subscribe(this._collection);
model.removed = false;
}, window.cvat.player.frames.current);
// End of undo/redo code
this._collection.update();
// Undo/redo code
let model = this._collection.shapes.slice(-1)[0];
window.cvat.addAction('Paste Object', () => {
model.removed = true;
model.unsubscribe(this._collection);
}, () => {
model.subscribe(this._collection);
model.removed = false;
}, window.cvat.player.frames.current);
// End of undo/redo code
this._collection.update();
}
}
propagateToFrames() {
let numOfFrames = this._propagateFrames;
if (this._shape.type && Number.isInteger(numOfFrames)) {
let bbRect = null;
let polyPoints = null;
let object = null;
if (this._shape.type === 'box') {
bbRect = {
x: this._shape.position.xtl,
y: this._shape.position.ytl,
height: this._shape.position.ybr - this._shape.position.ytl,
width: this._shape.position.xbr - this._shape.position.xtl,
let box = {
xtl: this._shape.position.xtl,
ytl: this._shape.position.ytl,
xbr: this._shape.position.xbr,
ybr: this._shape.position.ybr,
};
object = this._makeObject(box, null, false);
}
else {
polyPoints = this._shape.position.points;
object = this._makeObject(null, this._shape.position.points, false);
}
let object = this._makeObject(bbRect, polyPoints, false);
Logger.addEvent(Logger.EventType.propagateObject, {
count: numOfFrames,
});
if (object) {
Logger.addEvent(Logger.EventType.propagateObject, {
count: numOfFrames,
});
let addedObjects = [];
while (numOfFrames > 0 && (object.frame + 1 <= window.cvat.player.frames.stop)) {
object.frame ++;
object.z_order = this._collection.zOrder(object.frame).max;
this._collection.add(object, `annotation_${this._shape.type}`);
addedObjects.push(this._collection.shapes.slice(-1)[0]);
numOfFrames --;
}
let imageSizes = window.cvat.job.images.original_size;
let startFrame = window.cvat.player.frames.start;
let originalImageSize = imageSizes[object.frame - startFrame] || imageSizes[0];
// Undo/redo code
window.cvat.addAction('Propagate Object', () => {
for (let object of addedObjects) {
object.removed = true;
object.unsubscribe(this._collection);
let addedObjects = [];
while (numOfFrames > 0 && (object.frame + 1 <= window.cvat.player.frames.stop)) {
object.frame ++;
numOfFrames --;
// Propagate only for frames with same size
let imageSize = imageSizes[object.frame - startFrame] || imageSizes[0];
if ((imageSize.width != originalImageSize.width) || (imageSize.height != originalImageSize.height)) {
continue;
}
object.z_order = this._collection.zOrder(object.frame).max;
this._collection.add(object, `annotation_${this._shape.type}`);
addedObjects.push(this._collection.shapes.slice(-1)[0]);
}
}, () => {
for (let object of addedObjects) {
object.removed = false;
object.subscribe(this._collection);
if (addedObjects.length) {
// Undo/redo code
window.cvat.addAction('Propagate Object', () => {
for (let object of addedObjects) {
object.removed = true;
object.unsubscribe(this._collection);
}
}, () => {
for (let object of addedObjects) {
object.removed = false;
object.subscribe(this._collection);
}
}, window.cvat.player.frames.current);
// End of undo/redo code
}
}, window.cvat.player.frames.current);
// End of undo/redo code
}
}
}
......@@ -264,7 +269,10 @@ class ShapeBufferController {
pasteToFrame(e, bbRect, polyPoints) {
if (this._model.pasteMode) {
this._model.pasteToFrame(bbRect, polyPoints);
if (bbRect || polyPoints) {
this._model.pasteToFrame(bbRect, polyPoints);
}
if (!e.ctrlKey) {
this._model.switchPaste();
}
......@@ -298,35 +306,37 @@ class ShapeBufferView {
_drawShapeView() {
let scale = window.cvat.player.geometry.scale;
let points = this._shape.position.points ?
window.cvat.translate.points.actualToCanvas(this._shape.position.points) : null;
switch (this._shape.type) {
case 'box': {
let width = this._shape.position.xbr - this._shape.position.xtl;
let height = this._shape.position.ybr - this._shape.position.ytl;
this._shape.position = window.cvat.translate.box.actualToCanvas(this._shape.position);
this._shapeView = this._frameContent.rect(width, height)
.move(this._shape.position.xtl, this._shape.position.ytl)
.addClass('shapeCreation').attr({
.move(this._shape.position.xtl, this._shape.position.ytl).addClass('shapeCreation').attr({
'stroke-width': STROKE_WIDTH / scale,
});
break;
}
case 'polygon':
this._shapeView = this._frameContent.polygon(this._shape.position.points).addClass('shapeCreation').attr({
this._shapeView = this._frameContent.polygon(points).addClass('shapeCreation').attr({
'stroke-width': STROKE_WIDTH / scale,
});
break;
case 'polyline':
this._shapeView = this._frameContent.polyline(this._shape.position.points).addClass('shapeCreation').attr({
this._shapeView = this._frameContent.polyline(points).addClass('shapeCreation').attr({
'stroke-width': STROKE_WIDTH / scale,
});
break;
case 'points':
this._shapeView = this._frameContent.polyline(this._shape.position.points).addClass('shapeCreation').attr({
this._shapeView = this._frameContent.polyline(points).addClass('shapeCreation').attr({
'stroke-width': 0,
});
this._shapeViewGroup = this._frameContent.group();
for (let point of PolyShapeModel.convertStringToNumberArray(this._shape.position.points)) {
for (let point of PolyShapeModel.convertStringToNumberArray(points)) {
let radius = POINT_RADIUS * 2 / window.cvat.player.geometry.scale;
let scaledStroke = STROKE_WIDTH / window.cvat.player.geometry.scale;
this._shapeViewGroup.circle(radius).move(point.x - radius / 2, point.y - radius / 2)
......@@ -344,6 +354,7 @@ class ShapeBufferView {
_moveShapeView(pos) {
let rect = this._shapeView.node.getBBox();
this._shapeView.move(pos.x - rect.width / 2, pos.y - rect.height / 2);
if (this._shapeViewGroup) {
let rect = this._shapeViewGroup.node.getBBox();
......@@ -362,25 +373,58 @@ class ShapeBufferView {
_enableEvents() {
this._frameContent.on('mousemove.buffer', (e) => {
let pos = translateSVGPos(this._frameContent.node, e.clientX, e.clientY);
let pos = window.cvat.translate.point.clientToCanvas(this._frameContent.node, e.clientX, e.clientY);
this._shapeView.style('visibility', '');
this._moveShapeView(pos);
});
this._frameContent.on('mousedown.buffer', (e) => {
if (e.which != 1) return;
let rect = this._shapeView.node.getBBox();
if (this._shape.type != 'box') {
let points = PolyShapeModel.convertStringToNumberArray(this._shapeView.attr('points'));
for (let point of points) {
point.x = Math.clamp(point.x, 0, window.cvat.player.geometry.frameWidth);
point.y = Math.clamp(point.y, 0, window.cvat.player.geometry.frameHeight);
let actualPoints = window.cvat.translate.points.canvasToActual(this._shapeView.attr('points'));
let frameWidth = window.cvat.player.geometry.frameWidth;
let frameHeight = window.cvat.player.geometry.frameHeight;
actualPoints = PolyShapeModel.convertStringToNumberArray(actualPoints);
for (let point of actualPoints) {
point.x = Math.clamp(point.x, 0, frameWidth);
point.y = Math.clamp(point.y, 0, frameHeight);
}
actualPoints = PolyShapeModel.convertNumberArrayToString(actualPoints);
// Set clamped points to a view in order to get an updated bounding box for a poly shape
this._shapeView.attr('points', window.cvat.translate.points.actualToCanvas(actualPoints));
// Get an updated bounding box for check it area
let polybox = this._shapeView.node.getBBox();
let w = polybox.width;
let h = polybox.height;
let area = w * h;
let type = this._shape.type;
if (area > AREA_TRESHOLD || type === 'points' || type === 'polyline' && (w >= AREA_TRESHOLD || h >= AREA_TRESHOLD)) {
this._controller.pasteToFrame(e, null, actualPoints);
}
else {
this._controller.pasteToFrame(e, null, null);
}
points = PolyShapeModel.convertNumberArrayToString(points);
this._controller.pasteToFrame(e, rect, points);
}
else {
this._controller.pasteToFrame(e, rect);
let frameWidth = window.cvat.player.geometry.frameWidth;
let frameHeight = window.cvat.player.geometry.frameHeight;
let rect = window.cvat.translate.box.canvasToActual(this._shapeView.node.getBBox());
let box = {};
box.xtl = Math.clamp(rect.x, 0, frameWidth);
box.ytl = Math.clamp(rect.y, 0, frameHeight);
box.xbr = Math.clamp(rect.x + rect.width, 0, frameWidth);
box.ybr = Math.clamp(rect.y + rect.height, 0, frameHeight);
if ((box.xbr - box.xtl) * (box.ybr - box.ytl) >= AREA_TRESHOLD) {
this._controller.pasteToFrame(e, box, null);
}
else {
this._controller.pasteToFrame(e, null, null);
}
}
});
......
......@@ -1116,6 +1116,7 @@ class ShapeCollectionView {
constructor(collectionModel, collectionController) {
collectionModel.subscribe(this);
this._controller = collectionController;
this._frameBackground = $('#frameBackground');
this._frameContent = SVG.adopt($('#frameContent')[0]);
this._UIContent = $('#uiContent');
this._labelsContent = $('#labelsContent');
......@@ -1229,12 +1230,16 @@ class ShapeCollectionView {
return;
}
let pos = translateSVGPos(this._frameContent.node, e.clientX, e.clientY);
if (!window.cvat.mode) {
this._controller.selectShape(pos, false);
}
let frameHeight = window.cvat.player.geometry.frameHeight;
let frameWidth = window.cvat.player.geometry.frameWidth;
let pos = window.cvat.translate.point.clientToCanvas(this._frameBackground[0], e.clientX, e.clientY);
if (pos.x >= 0 && pos.y >= 0 && pos.x <= frameWidth && pos.y <= frameHeight) {
if (!window.cvat.mode) {
this._controller.selectShape(pos, false);
}
this._controller.setLastPosition(pos);
this._controller.setLastPosition(pos);
}
}.bind(this));
$('#shapeContextMenu li').click((e) => {
......
......@@ -234,22 +234,6 @@ class ShapeCreatorView {
this._polyShapeSizeInput.on('keydown', function(e) {
e.stopPropagation();
});
this._playerFrame.on('mousemove', function(e) {
// Save last coordinates in order to draw aim
this._aimCoord = translateSVGPos(this._frameContent.node, e.clientX, e.clientY);
if (this._aim) {
this._aim.x.attr({
y1: this._aimCoord.y,
y2: this._aimCoord.y,
});
this._aim.y.attr({
x1: this._aimCoord.x,
x2: this._aimCoord.x,
});
}
}.bind(this));
}
......@@ -329,29 +313,36 @@ class ShapeCreatorView {
});
// Also we need callback on drawdone event for get points
this._drawInstance.on('drawdone', function(e) {
let points = PolyShapeModel.convertStringToNumberArray(e.target.getAttribute('points'));
for (let point of points) {
point.x = Math.clamp(point.x, 0, window.cvat.player.geometry.frameWidth);
point.y = Math.clamp(point.y, 0, window.cvat.player.geometry.frameHeight);
}
let actualPoints = window.cvat.translate.points.canvasToActual(e.target.getAttribute('points'));
actualPoints = PolyShapeModel.convertStringToNumberArray(actualPoints);
// Min 2 points for polyline and 3 points for polygon
if (points.length) {
if (this._type === 'polyline' && points.length < 2) {
if (actualPoints.length) {
if (this._type === 'polyline' && actualPoints.length < 2) {
showMessage("Min 2 points must be for polyline drawing.");
}
else if (this._type === 'polygon' && points.length < 3) {
else if (this._type === 'polygon' && actualPoints.length < 3) {
showMessage("Min 3 points must be for polygon drawing.");
}
else {
points = PolyShapeModel.convertNumberArrayToString(points);
// Update points in view in order to get updated box
e.target.setAttribute('points', points);
let box = e.target.getBBox();
if (box.width * box.height >= AREA_TRESHOLD || this._type === 'points' ||
this._type === 'polyline' && (box.width >= AREA_TRESHOLD || box.height >= AREA_TRESHOLD)) {
this._controller.finish({points: e.target.getAttribute('points')}, this._type);
let frameWidth = window.cvat.player.geometry.frameWidth;
let frameHeight = window.cvat.player.geometry.frameHeight;
for (let point of actualPoints) {
point.x = Math.clamp(point.x, 0, frameWidth);
point.y = Math.clamp(point.y, 0, frameHeight);
}
actualPoints = PolyShapeModel.convertNumberArrayToString(actualPoints);
// Update points in a view in order to get an updated box
e.target.setAttribute('points', window.cvat.translate.points.actualToCanvas(actualPoints));
let polybox = e.target.getBBox();
let w = polybox.width;
let h = polybox.height;
let area = w * h;
let type = this.type;
if (area >= AREA_TRESHOLD || type === 'points' || type === 'polyline' && (w >= AREA_TRESHOLD || h >= AREA_TRESHOLD)) {
this._controller.finish({points: actualPoints}, type);
}
}
}
......@@ -373,19 +364,22 @@ class ShapeCreatorView {
sizeUI.rm();
sizeUI = null;
}
let result = {
xtl: Math.max(0, +e.target.getAttribute('x')),
ytl: Math.max(0, +e.target.getAttribute('y')),
xbr: Math.min(window.cvat.player.geometry.frameWidth, +e.target.getAttribute('x') + +e.target.getAttribute('width')),
ybr: Math.min(window.cvat.player.geometry.frameHeight, +e.target.getAttribute('y') + +e.target.getAttribute('height')),
};
let frameWidth = window.cvat.player.geometry.frameWidth;
let frameHeight = window.cvat.player.geometry.frameHeight;
let rect = window.cvat.translate.box.canvasToActual(e.target.getBBox());
let box = {};
box.xtl = Math.clamp(rect.x, 0, frameWidth);
box.ytl = Math.clamp(rect.y, 0, frameHeight);
box.xbr = Math.clamp(rect.x + rect.width, 0, frameWidth);
box.ybr = Math.clamp(rect.y + rect.height, 0, frameHeight);
if (this._mode === 'interpolation') {
result.outside = false;
box.outside = false;
}
if ((result.ybr - result.ytl) * (result.xbr - result.xtl) >= AREA_TRESHOLD) {
this._controller.finish(result, this._type);
if ((box.ybr - box.ytl) * (box.xbr - box.xtl) >= AREA_TRESHOLD) {
this._controller.finish(box, this._type);
}
this._controller.switchCreateMode(true);
......@@ -434,37 +428,6 @@ class ShapeCreatorView {
throw Error(`Bad type found ${this._type}`);
}
this._playerFrame.on('click.shapeCreation', (e) => {
if (e.target === this._playerFrame[0]) {
let original = e.originalEvent;
Object.defineProperty(original, 'clientX', {
value: original.clientX,
writable: true,
});
Object.defineProperty(original, 'clientY', {
value: original.clientY,
writable: true,
});
let svgNodePos = this._frameContent.node.getBoundingClientRect();
original.clientX = Math.clamp(original.clientX, svgNodePos.left, svgNodePos.right);
original.clientY = Math.clamp(original.clientY, svgNodePos.top, svgNodePos.bottom);
if (this._type === 'box') {
this._drawInstance.draw(original);
}
else {
for (let point of this._drawInstance.array().value) {
point[0] = Math.clamp(point[0], 0, window.cvat.player.geometry.frameWidth);
point[1] = Math.clamp(point[1], 0, window.cvat.player.geometry.frameHeight);
}
this._drawInstance.draw('point', original);
}
}
});
this._drawInstance.attr({
'z_order': Number.MAX_SAFE_INTEGER,
});
......@@ -480,13 +443,13 @@ class ShapeCreatorView {
_drawAim() {
if (!(this._aim)) {
this._aim = {
x: this._frameContent.line(0, this._aimCoord.y, window.cvat.player.geometry.frameWidth, this._aimCoord.y)
x: this._frameContent.line(0, this._aimCoord.y, this._frameContent.node.clientWidth, this._aimCoord.y)
.attr({
'stroke-width': STROKE_WIDTH / this._scale,
'stroke': 'red',
'z_order': Number.MAX_SAFE_INTEGER,
}).addClass('aim'),
y: this._frameContent.line(this._aimCoord.x, 0, this._aimCoord.x, window.cvat.player.geometry.frameHeight)
y: this._frameContent.line(this._aimCoord.x, 0, this._aimCoord.x, this._frameContent.node.clientHeight)
.attr({
'stroke-width': STROKE_WIDTH / this._scale,
'stroke': 'red',
......@@ -512,6 +475,20 @@ class ShapeCreatorView {
if (!['polygon', 'polyline', 'points'].includes(this._type)) {
this._drawAim();
this._playerFrame.on('mousemove.shapeCreatorAIM', (e) => {
this._aimCoord = window.cvat.translate.point.clientToCanvas(this._frameContent.node, e.clientX, e.clientY);
if (this._aim) {
this._aim.x.attr({
y1: this._aimCoord.y,
y2: this._aimCoord.y,
});
this._aim.y.attr({
x1: this._aimCoord.x,
x2: this._aimCoord.x,
});
}
});
}
this._createButton.text("Stop Creation");
......@@ -519,18 +496,19 @@ class ShapeCreatorView {
this._create();
}
else {
this._playerFrame.off('mousemove.shapeCreatorAIM');
this._removeAim();
this._aimCoord = {
x: 0,
y: 0
};
this._cancel = true;
this._createButton.text("Create Shape");
document.oncontextmenu = null;
this._playerFrame.off('click.shapeCreation');
if (this._drawInstance) {
// if need save current result for poly shape, do it.
// drawInstance and env will clean in the future when
// drawdone handler will call switchCreateMode with force argument
// also need draw min one point. Otherwise errors occur in SVG.draw.js on done event
// We save current result for poly shape if it's need
// drawInstance will be removed after save when drawdone handler calls switchCreateMode with force argument
if (model.saveCurrent && this._type != 'box') {
// FIXME: Error occured in svg.draw.js if no points was drawed and done, cancel or stop action applied
this._drawInstance.draw('done');
}
else {
......
......@@ -186,11 +186,12 @@ class ShapeGrouperView {
_enableEvents() {
this._frameContent.on('mousedown.grouper', (e) => {
this._initPoint = translateSVGPos(this._frameContent[0], e.clientX, e.clientY);
this._initPoint = window.cvat.translate.point.clientToCanvas(this._frameContent[0], e.clientX, e.clientY);
});
this._frameContent.on('mousemove.grouper', (e) => {
let currentPoint = translateSVGPos(this._frameContent[0], e.clientX, e.clientY);
let currentPoint = window.cvat.translate.point.clientToCanvas(this._frameContent[0], e.clientX, e.clientY);
if (this._initPoint) {
if (!this._rectSelector) {
this._rectSelector = $(document.createElementNS('http://www.w3.org/2000/svg', 'rect'));
......
......@@ -609,7 +609,7 @@ class BoxModel extends ShapeModel {
return Object.assign({},
this._positions[this._frame],
{
outside: false
outside: this._frame != frame
}
);
}
......@@ -868,16 +868,42 @@ class PolyShapeModel extends ShapeModel {
}
_interpolatePosition(frame) {
if (this._type.startsWith('annotation')) {
return Object.assign({},
this._positions[this._frame],
{
outside: this._frame != frame
}
);
}
let [leftFrame, rightFrame] = this._neighboringFrames(frame);
if (frame in this._positions) {
return Object.assign({}, this._positions[frame], {
outside: false
});
leftFrame = frame;
}
else {
return {
outside: true
};
let leftPos = null;
let rightPos = null;
if (leftFrame != null) leftPos = this._positions[leftFrame];
if (rightFrame != null) rightPos = this._positions[rightFrame];
if (!leftPos) {
if (rightPos) {
return Object.assign({}, rightPos, {
outside: true,
});
}
else {
return {
outside: true
};
}
}
return Object.assign({}, leftPos, {
outside: leftFrame != frame,
});
}
updatePosition(frame, position, silent) {
......@@ -1405,6 +1431,8 @@ class ShapeView extends Listener {
this._shapeContextMenu = $('#shapeContextMenu');
this._pointContextMenu = $('#pointContextMenu');
this._rightBorderFrame = $('#playerFrame')[0].offsetWidth;
shapeModel.subscribe(this);
}
......@@ -2415,24 +2443,18 @@ class ShapeView extends Listener {
this._uis.shape.attr('stroke-width', STROKE_WIDTH / scale);
}
if (this._uis.text && this._uis.text.node.parentElement) {
let revscale = 1 / scale;
let shapeBBox = this._uis.shape.node.getBBox();
let textBBox = this._uis.text.node.getBBox();
let textBBox = this._uis.text.node.getBBox()
let x = shapeBBox.x + shapeBBox.width + TEXT_MARGIN;
let x = shapeBBox.x + shapeBBox.width + TEXT_MARGIN * revscale;
let y = shapeBBox.y;
if (x + textBBox.width * revscale > window.cvat.player.geometry.frameWidth) {
x = shapeBBox.x - TEXT_MARGIN - textBBox.width * revscale;
if (x < 0) {
x = shapeBBox.x + TEXT_MARGIN;
}
}
if (y + textBBox.height * revscale > window.cvat.player.geometry.frameHeight) {
y = Math.max(0, window.cvat.player.geometry.frameHeight - textBBox.height * revscale);
let transl = window.cvat.translate.point;
let canvas = this._scenes.svg.node;
if (transl.canvasToClient(canvas, x + textBBox.width * revscale, 0).x > this._rightBorderFrame) {
x = shapeBBox.x + TEXT_MARGIN * revscale;
}
this._uis.text.move(x / revscale, y / revscale);
......@@ -2752,7 +2774,7 @@ class BoxView extends ShapeView {
_buildPosition() {
let shape = this._uis.shape.node;
return {
return window.cvat.translate.box.canvasToActual({
xtl: +shape.getAttribute('x'),
ytl: +shape.getAttribute('y'),
xbr: +shape.getAttribute('x') + +shape.getAttribute('width'),
......@@ -2760,13 +2782,12 @@ class BoxView extends ShapeView {
occluded: this._uis.shape.hasClass('occludedShape'),
outside: false, // if drag or resize possible, track is not outside
z_order: +shape.getAttribute('z_order'),
};
});
}
_drawShapeUI(position) {
let xtl = position.xtl;
let ytl = position.ytl;
position = window.cvat.translate.box.actualToCanvas(position);
let width = position.xbr - position.xtl;
let height = position.ybr - position.ytl;
......@@ -2776,7 +2797,7 @@ class BoxView extends ShapeView {
'stroke-width': STROKE_WIDTH / window.cvat.player.geometry.scale,
'z_order': position.z_order,
'fill-opacity': this._appearance.fillOpacity
}).move(xtl, ytl).addClass('shape');
}).move(position.xtl, position.ytl).addClass('shape');
ShapeView.prototype._drawShapeUI.call(this);
}
......@@ -2791,12 +2812,14 @@ class BoxView extends ShapeView {
oldMask.remove();
}
let size = {
let size = window.cvat.translate.box.actualToCanvas({
x: 0,
y: 0,
width: window.cvat.player.geometry.frameWidth,
height: window.cvat.player.geometry.frameHeight
};
});
pos = window.cvat.translate.box.actualToCanvas(pos);
let excludeField = this._scenes.svg.rect(size.width, size.height).move(size.x, size.y).fill('#666');
let includeField = this._scenes.svg.rect(pos.xbr - pos.xtl, pos.ybr - pos.ytl).move(pos.xtl, pos.ytl);
......@@ -2822,7 +2845,7 @@ class PolyShapeView extends ShapeView {
_buildPosition() {
return {
points: this._uis.shape.node.getAttribute('points'),
points: window.cvat.translate.points.canvasToActual(this._uis.shape.node.getAttribute('points')),
occluded: this._uis.shape.hasClass('occludedShape'),
outside: false,
z_order: +this._uis.shape.node.getAttribute('z_order'),
......@@ -2840,15 +2863,17 @@ class PolyShapeView extends ShapeView {
oldMask.remove();
}
let size = {
let size = window.cvat.translate.box.actualToCanvas({
x: 0,
y: 0,
width: window.cvat.player.geometry.frameWidth,
height: window.cvat.player.geometry.frameHeight
};
});
let points = window.cvat.translate.points.actualToCanvas(pos.points);
let excludeField = this._scenes.svg.rect(size.width, size.height).move(size.x, size.y).fill('#666');
let includeField = this._scenes.svg.polygon(pos.points);
let includeField = this._scenes.svg.polygon(points);
this._scenes.svg.mask().add(excludeField).add(includeField).fill('black').attr('id', 'outsideMask');
this._scenes.svg.rect(size.width, size.height).move(size.x, size.y).attr({
mask: 'url(#outsideMask)',
......@@ -2879,6 +2904,7 @@ class PolyShapeView extends ShapeView {
});
e.preventDefault();
e.stopPropagation();
});
point.on('dblclick.polyshapeEditor', (e) => {
......@@ -2940,7 +2966,8 @@ class PolygonView extends PolyShapeView {
}
_drawShapeUI(position) {
this._uis.shape = this._scenes.svg.polygon(position.points).fill(this._appearance.colors.shape).attr({
let points = window.cvat.translate.points.actualToCanvas(position.points);
this._uis.shape = this._scenes.svg.polygon(points).fill(this._appearance.colors.shape).attr({
'fill': this._appearance.fill || this._appearance.colors.shape,
'stroke': this._appearance.stroke || this._appearance.colors.shape,
'stroke-width': STROKE_WIDTH / window.cvat.player.geometry.scale,
......@@ -2980,7 +3007,8 @@ class PolylineView extends PolyShapeView {
_drawShapeUI(position) {
this._uis.shape = this._scenes.svg.polyline(position.points).fill(this._appearance.colors.shape).attr({
let points = window.cvat.translate.points.actualToCanvas(position.points);
this._uis.shape = this._scenes.svg.polyline(points).fill(this._appearance.colors.shape).attr({
'stroke': this._appearance.stroke || this._appearance.colors.shape,
'stroke-width': STROKE_WIDTH / window.cvat.player.geometry.scale,
'z_order': position.z_order,
......@@ -3122,17 +3150,19 @@ class PointsView extends PolyShapeView {
if (!this._controller.hiddenShape) {
let interpolation = this._controller.interpolate(window.cvat.player.frames.current);
if (interpolation.position.points) {
this._drawPointMarkers(interpolation.position);
let points = window.cvat.translate.points.actualToCanvas(interpolation.position.points);
this._drawPointMarkers(Object.assign(interpolation.position.points, {points: points}));
}
}
}
_drawShapeUI(position) {
this._uis.shape = this._scenes.svg.polyline(position.points).addClass('shape points').attr({
let points = window.cvat.translate.points.actualToCanvas(position.points);
this._uis.shape = this._scenes.svg.polyline(points).addClass('shape points').attr({
'z_order': position.z_order,
});
this._drawPointMarkers(position);
this._drawPointMarkers(Object.assign(position, {points: points}));
ShapeView.prototype._drawShapeUI.call(this);
}
......
......@@ -263,6 +263,7 @@
fill: white;
text-shadow: 0px 0px 3px black;
cursor: default;
pointer-events: none;
}
.highlightedShape {
......@@ -460,6 +461,7 @@
#frameContent {
position: absolute;
z-index: 1;
outline: 10px solid black;
-moz-transform-origin: top left;
-webkit-transform-origin: top left;
}
......
......@@ -35,6 +35,7 @@
<script type="text/javascript" src="{% static 'engine/js/server.js' %}"></script>
<script type="text/javascript" src="{% static 'engine/js/listener.js' %}"></script>
<script type="text/javascript" src="{% static 'engine/js/history.js' %}"></script>
<script type="text/javascript" src="{% static 'engine/js/coordinateTranslator.js' %}"></script>
<script type="text/javascript" src="{% static 'engine/js/labelsInfo.js' %}"></script>
<script type="text/javascript" src="{% static 'engine/js/annotationParser.js' %}"></script>
<script type="text/javascript" src="{% static 'engine/js/attributeAnnotationMode.js' %}"></script>
......
......@@ -41,7 +41,6 @@ module.exports = {
'AnnotationParser': true,
// from annotationUI.js
'callAnnotationUI': true,
'translateSVGPos': true,
'blurAllElements': true,
'drawBoxSize': true,
'copyToClipboard': true,
......@@ -123,5 +122,7 @@ module.exports = {
'PolyshapeEditorModel': true,
'PolyshapeEditorController': true,
'PolyshapeEditorView': true,
// from coordinateTranslator
'CoordinateTranslator': true,
},
};
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册