diff --git a/CHANGELOG.md b/CHANGELOG.md index 256faec23847..d1cddd82c2bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add Cityscapes format () - Add Open Images V6 format () - Rotated bounding boxes () +- Player option: Smooth image when zoom-in, enabled by default () ### Changed - TDB diff --git a/cvat-canvas/package-lock.json b/cvat-canvas/package-lock.json index 237dcbd7c20f..56dfe858e642 100644 --- a/cvat-canvas/package-lock.json +++ b/cvat-canvas/package-lock.json @@ -1,12 +1,12 @@ { "name": "cvat-canvas", - "version": "2.9.1", + "version": "2.10.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "cvat-canvas", - "version": "2.9.1", + "version": "2.10.0", "license": "MIT", "dependencies": { "svg.draggable.js": "2.2.2", diff --git a/cvat-canvas/package.json b/cvat-canvas/package.json index e22bc101587e..9b913e15e906 100644 --- a/cvat-canvas/package.json +++ b/cvat-canvas/package.json @@ -1,6 +1,6 @@ { "name": "cvat-canvas", - "version": "2.9.1", + "version": "2.10.0", "description": "Part of Computer Vision Annotation Tool which presents its canvas library", "main": "src/canvas.ts", "scripts": { diff --git a/cvat-canvas/src/scss/canvas.scss b/cvat-canvas/src/scss/canvas.scss index 2eccb9ee096c..7cf9242e0b10 100644 --- a/cvat-canvas/src/scss/canvas.scss +++ b/cvat-canvas/src/scss/canvas.scss @@ -229,6 +229,17 @@ polyline.cvat_canvas_shape_splitting { } } +.cvat_canvas_pixelized { + image-rendering: optimizeSpeed; /* Legal fallback */ + image-rendering: -moz-crisp-edges; /* Firefox */ + image-rendering: -o-crisp-edges; /* Opera */ + image-rendering: -webkit-optimize-contrast; /* Safari */ + image-rendering: optimize-contrast; /* CSS3 Proposed */ + image-rendering: crisp-edges; /* CSS4 Proposed */ + image-rendering: pixelated; /* CSS4 Proposed */ + -ms-interpolation-mode: nearest-neighbor; /* IE8+ */ +} + #cvat_canvas_wrapper { width: calc(100% - 10px); height: calc(100% - 10px); @@ -273,6 +284,8 @@ polyline.cvat_canvas_shape_splitting { } #cvat_canvas_bitmap { + @extend .cvat_canvas_pixelized; + pointer-events: none; position: absolute; z-index: 4; diff --git a/cvat-canvas/src/typescript/canvasModel.ts b/cvat-canvas/src/typescript/canvasModel.ts index 22f571d3ad89..0f048a931641 100644 --- a/cvat-canvas/src/typescript/canvasModel.ts +++ b/cvat-canvas/src/typescript/canvasModel.ts @@ -52,6 +52,7 @@ export enum CuboidDrawingMethod { } export interface Configuration { + smoothImage?: boolean; autoborders?: boolean; displayAllText?: boolean; undefinedAttrValue?: string; @@ -652,23 +653,21 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel { if (typeof configuration.autoborders === 'boolean') { this.data.configuration.autoborders = configuration.autoborders; } - + if (typeof configuration.smoothImage === 'boolean') { + this.data.configuration.smoothImage = configuration.smoothImage; + } if (typeof configuration.undefinedAttrValue === 'string') { this.data.configuration.undefinedAttrValue = configuration.undefinedAttrValue; } - if (typeof configuration.forceDisableEditing === 'boolean') { this.data.configuration.forceDisableEditing = configuration.forceDisableEditing; } - if (typeof configuration.intelligentPolygonCrop === 'boolean') { this.data.configuration.intelligentPolygonCrop = configuration.intelligentPolygonCrop; } - if (typeof configuration.forceFrameUpdate === 'boolean') { this.data.configuration.forceFrameUpdate = configuration.forceFrameUpdate; } - if (typeof configuration.creationOpacity === 'number') { this.data.configuration.creationOpacity = configuration.creationOpacity; } diff --git a/cvat-canvas/src/typescript/canvasView.ts b/cvat-canvas/src/typescript/canvasView.ts index 8503ca6d3e88..6f9587c742bc 100644 --- a/cvat-canvas/src/typescript/canvasView.ts +++ b/cvat-canvas/src/typescript/canvasView.ts @@ -1169,15 +1169,16 @@ export class CanvasViewImpl implements CanvasView, Listener { if (reason === UpdateReasons.CONFIG_UPDATED) { const { activeElement } = this; this.deactivate(); + const { configuration } = model; - if (model.configuration.displayAllText && !this.configuration.displayAllText) { + if (configuration.displayAllText && !this.configuration.displayAllText) { 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 (model.configuration.displayAllText === false && this.configuration.displayAllText) { + } else if (configuration.displayAllText === false && this.configuration.displayAllText) { for (const i in this.drawnStates) { if (i in this.svgTexts && Number.parseInt(i, 10) !== activeElement.clientID) { this.svgTexts[i].remove(); @@ -1186,7 +1187,15 @@ export class CanvasViewImpl implements CanvasView, Listener { } } - this.configuration = model.configuration; + if ('smoothImage' in configuration) { + if (configuration.smoothImage) { + this.background.classList.remove('cvat_canvas_pixelized'); + } else { + this.background.classList.add('cvat_canvas_pixelized'); + } + } + + this.configuration = configuration; this.activate(activeElement); this.editHandler.configurate(this.configuration); this.drawHandler.configurate(this.configuration); diff --git a/cvat-ui/package-lock.json b/cvat-ui/package-lock.json index f90e12606495..a408dbd9d2ad 100644 --- a/cvat-ui/package-lock.json +++ b/cvat-ui/package-lock.json @@ -1,12 +1,12 @@ { "name": "cvat-ui", - "version": "1.26.0", + "version": "1.27.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "cvat-ui", - "version": "1.26.0", + "version": "1.27.0", "license": "MIT", "dependencies": { "@ant-design/icons": "^4.6.3", diff --git a/cvat-ui/package.json b/cvat-ui/package.json index b69fa4d49800..ac2cf462d3f1 100644 --- a/cvat-ui/package.json +++ b/cvat-ui/package.json @@ -1,6 +1,6 @@ { "name": "cvat-ui", - "version": "1.26.0", + "version": "1.27.0", "description": "CVAT single-page application", "main": "src/index.tsx", "scripts": { diff --git a/cvat-ui/src/actions/settings-actions.ts b/cvat-ui/src/actions/settings-actions.ts index 6e9b5bfb9b7c..b68e29a5e551 100644 --- a/cvat-ui/src/actions/settings-actions.ts +++ b/cvat-ui/src/actions/settings-actions.ts @@ -22,6 +22,7 @@ export enum SettingsActionTypes { CHANGE_FRAME_STEP = 'CHANGE_FRAME_STEP', CHANGE_FRAME_SPEED = 'CHANGE_FRAME_SPEED', SWITCH_RESET_ZOOM = 'SWITCH_RESET_ZOOM', + SWITCH_SMOOTH_IMAGE = 'SWITCH_SMOOTH_IMAGE', CHANGE_BRIGHTNESS_LEVEL = 'CHANGE_BRIGHTNESS_LEVEL', CHANGE_CONTRAST_LEVEL = 'CHANGE_CONTRAST_LEVEL', CHANGE_SATURATION_LEVEL = 'CHANGE_SATURATION_LEVEL', @@ -166,6 +167,15 @@ export function switchResetZoom(resetZoom: boolean): AnyAction { }; } +export function switchSmoothImage(enabled: boolean): AnyAction { + return { + type: SettingsActionTypes.SWITCH_SMOOTH_IMAGE, + payload: { + smoothImage: enabled, + }, + }; +} + export function changeBrightnessLevel(level: number): AnyAction { return { type: SettingsActionTypes.CHANGE_BRIGHTNESS_LEVEL, diff --git a/cvat-ui/src/components/annotation-page/canvas/canvas-wrapper.tsx b/cvat-ui/src/components/annotation-page/canvas/canvas-wrapper.tsx index 3598ad49f556..632ad2473210 100644 --- a/cvat-ui/src/components/annotation-page/canvas/canvas-wrapper.tsx +++ b/cvat-ui/src/components/annotation-page/canvas/canvas-wrapper.tsx @@ -27,7 +27,7 @@ const MAX_DISTANCE_TO_OPEN_SHAPE = 50; interface Props { sidebarCollapsed: boolean; - canvasInstance: Canvas | Canvas3d; + canvasInstance: Canvas | Canvas3d | null; jobInstance: any; activatedStateID: number | null; activatedAttributeID: number | null; @@ -57,6 +57,7 @@ interface Props { contrastLevel: number; saturationLevel: number; resetZoom: boolean; + smoothImage: boolean; aamZoomMargin: number; showObjectsTextAlways: boolean; showAllInterpolationTracks: boolean; @@ -105,6 +106,7 @@ export default class CanvasWrapperComponent extends React.PureComponent { workspace, showProjections, selectedOpacity, + smoothImage, } = this.props; const { canvasInstance } = this.props as { canvasInstance: Canvas }; @@ -114,6 +116,7 @@ export default class CanvasWrapperComponent extends React.PureComponent { wrapper.appendChild(canvasInstance.html()); canvasInstance.configure({ + smoothImage, autoborders: automaticBordering, undefinedAttrValue: consts.UNDEFINED_ATTRIBUTE_VALUE, displayAllText: showObjectsTextAlways, @@ -144,6 +147,7 @@ export default class CanvasWrapperComponent extends React.PureComponent { activatedStateID, curZLayer, resetZoom, + smoothImage, grid, gridSize, gridOpacity, @@ -167,7 +171,8 @@ export default class CanvasWrapperComponent extends React.PureComponent { prevProps.automaticBordering !== automaticBordering || prevProps.showProjections !== showProjections || prevProps.intelligentPolygonCrop !== intelligentPolygonCrop || - prevProps.selectedOpacity !== selectedOpacity + prevProps.selectedOpacity !== selectedOpacity || + prevProps.smoothImage !== smoothImage ) { canvasInstance.configure({ undefinedAttrValue: consts.UNDEFINED_ATTRIBUTE_VALUE, @@ -176,6 +181,7 @@ export default class CanvasWrapperComponent extends React.PureComponent { showProjections, intelligentPolygonCrop, creationOpacity: selectedOpacity, + smoothImage, }); } @@ -418,7 +424,9 @@ export default class CanvasWrapperComponent extends React.PureComponent { private fitCanvas = (): void => { const { canvasInstance } = this.props; - canvasInstance.fitCanvas(); + if (canvasInstance) { + canvasInstance.fitCanvas(); + } }; private onCanvasMouseDown = (e: MouseEvent): void => { @@ -677,7 +685,7 @@ export default class CanvasWrapperComponent extends React.PureComponent { curZLayer, annotations, frameData, canvasInstance, } = this.props; - if (frameData !== null) { + if (frameData !== null && canvasInstance) { canvasInstance.setup( frameData, annotations.filter((e) => e.objectType !== ObjectType.TAG), diff --git a/cvat-ui/src/components/header/settings-modal/player-settings.tsx b/cvat-ui/src/components/header/settings-modal/player-settings.tsx index ddc203e1d3ec..69fcca2adc12 100644 --- a/cvat-ui/src/components/header/settings-modal/player-settings.tsx +++ b/cvat-ui/src/components/header/settings-modal/player-settings.tsx @@ -24,12 +24,14 @@ interface Props { frameSpeed: FrameSpeed; resetZoom: boolean; rotateAll: boolean; + smoothImage: boolean; canvasBackgroundColor: string; onChangeFrameStep(step: number): void; onChangeFrameSpeed(speed: FrameSpeed): void; onSwitchResetZoom(enabled: boolean): void; onSwitchRotateAll(rotateAll: boolean): void; onChangeCanvasBackgroundColor(color: string): void; + onSwitchSmoothImage(enabled: boolean): void; } export default function PlayerSettingsComponent(props: Props): JSX.Element { @@ -38,11 +40,13 @@ export default function PlayerSettingsComponent(props: Props): JSX.Element { frameSpeed, resetZoom, rotateAll, + smoothImage, canvasBackgroundColor, onChangeFrameStep, onChangeFrameSpeed, onSwitchResetZoom, onSwitchRotateAll, + onSwitchSmoothImage, onChangeCanvasBackgroundColor, } = props; @@ -176,6 +180,26 @@ export default function PlayerSettingsComponent(props: Props): JSX.Element { + + + + + { + onSwitchSmoothImage(event.target.checked); + }} + > + Smooth image + + + + Smooth image when zoom-in it + + + + ); } diff --git a/cvat-ui/src/containers/annotation-page/canvas/canvas-wrapper.tsx b/cvat-ui/src/containers/annotation-page/canvas/canvas-wrapper.tsx index 59b4af02d99c..d459087815f5 100644 --- a/cvat-ui/src/containers/annotation-page/canvas/canvas-wrapper.tsx +++ b/cvat-ui/src/containers/annotation-page/canvas/canvas-wrapper.tsx @@ -53,7 +53,7 @@ import { Canvas3d } from 'cvat-canvas3d-wrapper'; interface StateToProps { sidebarCollapsed: boolean; - canvasInstance: Canvas | Canvas3d; + canvasInstance: Canvas | Canvas3d | null; jobInstance: any; activatedStateID: number | null; activatedAttributeID: number | null; @@ -80,6 +80,7 @@ interface StateToProps { contrastLevel: number; saturationLevel: number; resetZoom: boolean; + smoothImage: boolean; aamZoomMargin: number; showObjectsTextAlways: boolean; showAllInterpolationTracks: boolean; @@ -155,6 +156,7 @@ function mapStateToProps(state: CombinedState): StateToProps { contrastLevel, saturationLevel, resetZoom, + smoothImage, }, workspace: { aamZoomMargin, @@ -201,6 +203,7 @@ function mapStateToProps(state: CombinedState): StateToProps { contrastLevel: contrastLevel / 100, saturationLevel: saturationLevel / 100, resetZoom, + smoothImage, aamZoomMargin, showObjectsTextAlways, showAllInterpolationTracks, diff --git a/cvat-ui/src/containers/header/settings-modal/player-settings.tsx b/cvat-ui/src/containers/header/settings-modal/player-settings.tsx index b723b7646c1f..36b9daa96f99 100644 --- a/cvat-ui/src/containers/header/settings-modal/player-settings.tsx +++ b/cvat-ui/src/containers/header/settings-modal/player-settings.tsx @@ -11,6 +11,7 @@ import { switchResetZoom, switchRotateAll, changeCanvasBackgroundColor, + switchSmoothImage, } from 'actions/settings-actions'; import { CombinedState, FrameSpeed } from 'reducers/interfaces'; @@ -19,6 +20,7 @@ interface StateToProps { frameSpeed: FrameSpeed; resetZoom: boolean; rotateAll: boolean; + smoothImage: boolean; canvasBackgroundColor: string; } @@ -28,6 +30,7 @@ interface DispatchToProps { onSwitchResetZoom(enabled: boolean): void; onSwitchRotateAll(rotateAll: boolean): void; onChangeCanvasBackgroundColor(color: string): void; + onSwitchSmoothImage(enabled: boolean): void; } function mapStateToProps(state: CombinedState): StateToProps { @@ -55,6 +58,9 @@ function mapDispatchToProps(dispatch: any): DispatchToProps { onChangeCanvasBackgroundColor(color: string): void { dispatch(changeCanvasBackgroundColor(color)); }, + onSwitchSmoothImage(enabled: boolean): void { + dispatch(switchSmoothImage(enabled)); + }, }; } diff --git a/cvat-ui/src/reducers/interfaces.ts b/cvat-ui/src/reducers/interfaces.ts index 00b1d3bc4674..5b6e81cc7603 100644 --- a/cvat-ui/src/reducers/interfaces.ts +++ b/cvat-ui/src/reducers/interfaces.ts @@ -617,6 +617,7 @@ export interface PlayerSettingsState { frameSpeed: FrameSpeed; resetZoom: boolean; rotateAll: boolean; + smoothImage: boolean; grid: boolean; gridSize: number; gridColor: GridColor; diff --git a/cvat-ui/src/reducers/settings-reducer.ts b/cvat-ui/src/reducers/settings-reducer.ts index 69a945d6ade9..1dbc29eebc5a 100644 --- a/cvat-ui/src/reducers/settings-reducer.ts +++ b/cvat-ui/src/reducers/settings-reducer.ts @@ -43,6 +43,7 @@ const defaultState: SettingsState = { frameSpeed: FrameSpeed.Usual, resetZoom: false, rotateAll: false, + smoothImage: true, grid: false, gridSize: 100, gridColor: GridColor.White, @@ -183,6 +184,15 @@ export default (state = defaultState, action: AnyAction): SettingsState => { }, }; } + case SettingsActionTypes.SWITCH_SMOOTH_IMAGE: { + return { + ...state, + player: { + ...state.player, + smoothImage: action.payload.smoothImage, + }, + }; + } case SettingsActionTypes.CHANGE_BRIGHTNESS_LEVEL: { return { ...state,