Skip to content

Commit

Permalink
Added options to switch text font size and position (#3972)
Browse files Browse the repository at this point in the history
  • Loading branch information
Boris Sekachev authored Dec 5, 2021
1 parent c79a4c1 commit ab351c2
Show file tree
Hide file tree
Showing 15 changed files with 254 additions and 49 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Google Cloud Storage support in UI (<https://github.com/openvinotoolkit/cvat/pull/3919>)
- Add project tasks paginations (<https://github.com/openvinotoolkit/cvat/pull/3910>)
- Add remove issue button (<https://github.com/openvinotoolkit/cvat/pull/3952>)
- Options to change font size & position of text labels on the canvas (<https://github.com/openvinotoolkit/cvat/pull/3972>)

### Changed
- TDB
Expand Down
45 changes: 41 additions & 4 deletions cvat-canvas/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions cvat-canvas/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "cvat-canvas",
"version": "2.10.0",
"version": "2.10.1",
"description": "Part of Computer Vision Annotation Tool which presents its canvas library",
"main": "src/canvas.ts",
"scripts": {
Expand All @@ -15,8 +15,9 @@
"not IE 11",
"> 2%"
],
"devDependencies": {},
"dependencies": {
"@types/polylabel": "^1.0.5",
"polylabel": "^1.1.0",
"svg.draggable.js": "2.2.2",
"svg.draw.js": "^2.0.4",
"svg.js": "2.7.1",
Expand Down
2 changes: 0 additions & 2 deletions cvat-canvas/src/scss/canvas.scss
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ polyline.cvat_shape_drawing_opacity {

.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;
Expand All @@ -47,7 +46,6 @@ polyline.cvat_shape_drawing_opacity {
}

.cvat_canvas_text_description {
font-size: 14px;
fill: yellow;
font-style: oblique 40deg;
}
Expand Down
10 changes: 10 additions & 0 deletions cvat-canvas/src/typescript/canvasModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ export interface Configuration {
smoothImage?: boolean;
autoborders?: boolean;
displayAllText?: boolean;
textFontSize?: number;
textPosition?: 'auto' | 'center';
undefinedAttrValue?: string;
showProjections?: boolean;
forceDisableEditing?: boolean;
Expand Down Expand Up @@ -647,6 +649,14 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel {
this.data.configuration.displayAllText = configuration.displayAllText;
}

if (typeof configuration.textFontSize === 'number') {
this.data.configuration.textFontSize = configuration.textFontSize;
}

if (['auto', 'center'].includes(configuration.textPosition)) {
this.data.configuration.textPosition = configuration.textPosition;
}

if (typeof configuration.showProjections === 'boolean') {
this.data.configuration.showProjections = configuration.showProjections;
}
Expand Down
125 changes: 89 additions & 36 deletions cvat-canvas/src/typescript/canvasView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
//
// SPDX-License-Identifier: MIT

import polylabel from 'polylabel';
import * as SVG from 'svg.js';

import 'svg.draggable.js';
Expand Down Expand Up @@ -683,11 +684,13 @@ export class CanvasViewImpl implements CanvasView, Listener {
for (const state of deleted) {
if (state.clientID in this.svgTexts) {
this.svgTexts[state.clientID].remove();
delete this.svgTexts[state.clientID];
}

this.svgShapes[state.clientID].off('click.canvas');
this.svgShapes[state.clientID].remove();
delete this.drawnStates[state.clientID];
delete this.svgShapes[state.clientID];
}

this.addObjects(created);
Expand Down Expand Up @@ -1175,7 +1178,6 @@ export class CanvasViewImpl implements CanvasView, Listener {
for (const i in this.drawnStates) {
if (!(i in this.svgTexts)) {
this.svgTexts[i] = this.addText(this.drawnStates[i]);
this.updateTextPosition(this.svgTexts[i], this.svgShapes[i]);
}
}
} else if (configuration.displayAllText === false && this.configuration.displayAllText) {
Expand All @@ -1187,15 +1189,25 @@ export class CanvasViewImpl implements CanvasView, Listener {
}
}

if ('smoothImage' in configuration) {
if (configuration.smoothImage) {
this.background.classList.remove('cvat_canvas_pixelized');
} else {
this.background.classList.add('cvat_canvas_pixelized');
}
const updateTextPosition = configuration.displayAllText !== this.configuration.displayAllText ||
configuration.textFontSize !== this.configuration.textFontSize ||
configuration.textPosition !== this.configuration.textPosition;

if (configuration.smoothImage === true) {
this.background.classList.remove('cvat_canvas_pixelized');
} else if (configuration.smoothImage === false) {
this.background.classList.add('cvat_canvas_pixelized');
}

this.configuration = configuration;
if (updateTextPosition) {
for (const i in this.drawnStates) {
if (i in this.svgTexts) {
this.updateTextPosition(this.svgTexts[i], this.svgShapes[i]);
}
}
}

this.activate(activeElement);
this.editHandler.configurate(this.configuration);
this.drawHandler.configurate(this.configuration);
Expand Down Expand Up @@ -2059,45 +2071,82 @@ export class CanvasViewImpl implements CanvasView, Listener {
// Update text position after corresponding box has been moved, resized, etc.
private updateTextPosition(text: SVG.Text, shape: SVG.Shape): void {
if (text.node.style.display === 'none') return; // wrong transformation matrix
const { rotation } = shape.transform();
let box = (shape.node as any).getBBox();

// Translate the whole box to the client coordinate system
const [x1, y1, x2, y2]: number[] = translateFromSVG(this.content, [
box.x,
box.y,
box.x + box.width,
box.y + box.height,
]);
const textFontSize = this.configuration.textFontSize || consts.DEFAULT_SHAPE_TEXT_SIZE;
const textPosition = this.configuration.textPosition || 'auto';

box = {
x: Math.min(x1, x2),
y: Math.min(y1, y2),
width: Math.max(x1, x2) - Math.min(x1, x2),
height: Math.max(y1, y2) - Math.min(y1, y2),
};
text.untransform();
text.style({ 'font-size': textFontSize });
const { rotation } = shape.transform();

// Find the best place for a text
let [clientX, clientY]: number[] = [box.x + box.width, box.y];
if (
clientX + ((text.node as any) as SVGTextElement)
.getBBox().width + consts.TEXT_MARGIN > this.canvas.offsetWidth
) {
[clientX, clientY] = [box.x, box.y];
let [clientX, clientY, clientCX, clientCY]: number[] = [0, 0, 0, 0];
if (textPosition === 'center') {
let cx = 0;
let cy = 0;
if (shape.type === 'rect') {
// for rectangle finding a center is simple
cx = +shape.attr('x') + +shape.attr('width') / 2;
cy = +shape.attr('y') + +shape.attr('height') / 2;
} else {
// for polyshapes we use special algorithm
const points = parsePoints(pointsToNumberArray(shape.attr('points')));
[cx, cy] = polylabel([points.map((point) => [point.x, point.y])]);
}

[clientX, clientY] = translateFromSVG(this.content, [cx, cy]);
// center is exactly clientX, clientY
clientCX = clientX;
clientCY = clientY;
} else {
let box = (shape.node as any).getBBox();

// Translate the whole box to the client coordinate system
const [x1, y1, x2, y2]: number[] = translateFromSVG(this.content, [
box.x,
box.y,
box.x + box.width,
box.y + box.height,
]);

clientCX = x1 + (x2 - x1) / 2;
clientCY = y1 + (y2 - y1) / 2;

box = {
x: Math.min(x1, x2),
y: Math.min(y1, y2),
width: Math.max(x1, x2) - Math.min(x1, x2),
height: Math.max(y1, y2) - Math.min(y1, y2),
};

// first try to put to the top right corner
[clientX, clientY] = [box.x + box.width, box.y];
if (
clientX + ((text.node as any) as SVGTextElement)
.getBBox().width + consts.TEXT_MARGIN > this.canvas.offsetWidth
) {
// if out of visible area, try to put text to top left corner
[clientX, clientY] = [box.x, box.y];
}
}

// Translate back to text SVG
const [x, y, cx, cy]: number[] = translateToSVG(this.text, [
// Translate found coordinates to text SVG
const [x, y, rotX, rotY]: number[] = translateToSVG(this.text, [
clientX + consts.TEXT_MARGIN,
clientY + consts.TEXT_MARGIN,
x1 + (x2 - x1) / 2,
y1 + (y2 - y1) / 2,
clientCX,
clientCY,
]);

const textBBox = ((text.node as any) as SVGTextElement).getBBox();
// Finally draw a text
text.move(x, y);
if (textPosition === 'center') {
text.move(x - textBBox.width / 2, y - textBBox.height / 2);
} else {
text.move(x, y);
}

if (rotation) {
text.rotate(rotation, cx, cy);
text.rotate(rotation, rotX, rotY);
}

for (const tspan of (text.lines() as any).members) {
Expand All @@ -2107,6 +2156,7 @@ export class CanvasViewImpl implements CanvasView, Listener {

private addText(state: any): SVG.Text {
const { undefinedAttrValue } = this.configuration;
const textFontSize = this.configuration.textFontSize || 12;
const {
label, clientID, attributes, source, descriptions,
} = state;
Expand All @@ -2117,7 +2167,9 @@ export class CanvasViewImpl implements CanvasView, Listener {

return this.adoptedText
.text((block): void => {
block.tspan(`${label.name} ${clientID} (${source})`).style('text-transform', 'uppercase');
block.tspan(`${label.name} ${clientID} (${source})`).style({
'text-transform': 'uppercase',
});
for (const desc of descriptions) {
block
.tspan(`${desc}`)
Expand All @@ -2140,6 +2192,7 @@ export class CanvasViewImpl implements CanvasView, Listener {
}
})
.move(0, 0)
.style({ 'font-size': textFontSize })
.addClass('cvat_canvas_text');
}

Expand Down
2 changes: 2 additions & 0 deletions cvat-canvas/src/typescript/consts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const ARROW_PATH = 'M13.162 6.284L.682.524a.483.483 0 0 0-.574.134.477.477 0 ' +
const BASE_PATTERN_SIZE = 5;
const SNAP_TO_ANGLE_RESIZE_DEFAULT = 0.1;
const SNAP_TO_ANGLE_RESIZE_SHIFT = 15;
const DEFAULT_SHAPE_TEXT_SIZE = 12;

export default {
BASE_STROKE_WIDTH,
Expand All @@ -37,4 +38,5 @@ export default {
BASE_PATTERN_SIZE,
SNAP_TO_ANGLE_RESIZE_DEFAULT,
SNAP_TO_ANGLE_RESIZE_SHIFT,
DEFAULT_SHAPE_TEXT_SIZE,
};
20 changes: 20 additions & 0 deletions cvat-ui/src/actions/settings-actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ export enum SettingsActionTypes {
CHANGE_FRAME_SPEED = 'CHANGE_FRAME_SPEED',
SWITCH_RESET_ZOOM = 'SWITCH_RESET_ZOOM',
SWITCH_SMOOTH_IMAGE = 'SWITCH_SMOOTH_IMAGE',
SWITCH_TEXT_FONT_SIZE = 'SWITCH_TEXT_FONT_SIZE',
SWITCH_TEXT_POSITION = 'SWITCH_TEXT_POSITION',
CHANGE_BRIGHTNESS_LEVEL = 'CHANGE_BRIGHTNESS_LEVEL',
CHANGE_CONTRAST_LEVEL = 'CHANGE_CONTRAST_LEVEL',
CHANGE_SATURATION_LEVEL = 'CHANGE_SATURATION_LEVEL',
Expand Down Expand Up @@ -176,6 +178,24 @@ export function switchSmoothImage(enabled: boolean): AnyAction {
};
}

export function switchTextFontSize(fontSize: number): AnyAction {
return {
type: SettingsActionTypes.SWITCH_TEXT_FONT_SIZE,
payload: {
fontSize,
},
};
}

export function switchTextPosition(position: 'auto' | 'center'): AnyAction {
return {
type: SettingsActionTypes.SWITCH_TEXT_POSITION,
payload: {
position,
},
};
}

export function changeBrightnessLevel(level: number): AnyAction {
return {
type: SettingsActionTypes.CHANGE_BRIGHTNESS_LEVEL,
Expand Down
Loading

0 comments on commit ab351c2

Please sign in to comment.