diff --git a/cvat-canvas/README.md b/cvat-canvas/README.md index fe216817f139..2b9f9201ba76 100644 --- a/cvat-canvas/README.md +++ b/cvat-canvas/README.md @@ -32,11 +32,6 @@ Canvas itself handles: ### API Methods ```ts - enum Rotation { - ANTICLOCKWISE90, - CLOCKWISE90, - } - enum RectDrawingMethod { CLASSIC = 'By 2 points', EXTREME_POINTS = 'By 4 points' @@ -79,7 +74,7 @@ Canvas itself handles: setZLayer(zLayer: number | null): void; setup(frameData: any, objectStates: any[]): void; activate(clientID: number, attributeID?: number): void; - rotate(rotation: Rotation, remember?: boolean): void; + rotate(frameAngle: number): void; focus(clientID: number, padding?: number): void; fit(): void; grid(stepX: number, stepY: number): void; @@ -147,7 +142,7 @@ Standard JS events are used. canvas.fitCanvas(); // Next you can use its API methods. For example: - canvas.rotate(window.Canvas.Rotation.CLOCKWISE90); + canvas.rotate(270); canvas.draw({ enabled: true, shapeType: 'rectangle', diff --git a/cvat-canvas/src/typescript/canvas.ts b/cvat-canvas/src/typescript/canvas.ts index 66af0ce8848d..c53c9d523c6a 100644 --- a/cvat-canvas/src/typescript/canvas.ts +++ b/cvat-canvas/src/typescript/canvas.ts @@ -3,7 +3,6 @@ // SPDX-License-Identifier: MIT import { - Rotation, DrawData, MergeData, SplitData, @@ -37,7 +36,7 @@ interface Canvas { setZLayer(zLayer: number | null): void; setup(frameData: any, objectStates: any[]): void; activate(clientID: number | null, attributeID?: number): void; - rotate(rotation: Rotation, remember?: boolean): void; + rotate(rotationAngle: number): void; focus(clientID: number, padding?: number): void; fit(): void; grid(stepX: number, stepY: number): void; @@ -97,8 +96,8 @@ class CanvasImpl implements Canvas { this.model.activate(clientID, attributeID); } - public rotate(rotation: Rotation, remember: boolean = false): void { - this.model.rotate(rotation, remember); + public rotate(rotationAngle: number): void { + this.model.rotate(rotationAngle); } public focus(clientID: number, padding: number = 0): void { @@ -140,7 +139,6 @@ class CanvasImpl implements Canvas { export { CanvasImpl as Canvas, - Rotation, CanvasVersion, RectDrawingMethod, }; diff --git a/cvat-canvas/src/typescript/canvasModel.ts b/cvat-canvas/src/typescript/canvasModel.ts index 1fc273a6811c..f72bc6324701 100644 --- a/cvat-canvas/src/typescript/canvasModel.ts +++ b/cvat-canvas/src/typescript/canvasModel.ts @@ -72,11 +72,6 @@ export enum FrameZoom { MAX = 10, } -export enum Rotation { - ANTICLOCKWISE90, - CLOCKWISE90, -} - export enum UpdateReasons { IMAGE_CHANGED = 'image_changed', IMAGE_ZOOMED = 'image_zoomed', @@ -135,7 +130,7 @@ export interface CanvasModel { setup(frameData: any, objectStates: any[]): void; activate(clientID: number | null, attributeID: number | null): void; - rotate(rotation: Rotation, remember: boolean): void; + rotate(rotationAngle: number): void; focus(clientID: number, padding: number): void; fit(): void; grid(stepX: number, stepY: number): void; @@ -166,7 +161,6 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel { gridSize: Size; left: number; objects: any[]; - rememberAngle: boolean; scale: number; top: number; zLayer: number | null; @@ -208,7 +202,6 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel { }, left: 0, objects: [], - rememberAngle: false, scale: 1, top: 0, zLayer: null, @@ -323,10 +316,6 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel { return; } - if (!this.data.rememberAngle) { - this.data.angle = 0; - } - this.data.imageSize = { height: (frameData.height as number), width: (frameData.width as number), @@ -355,16 +344,11 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel { this.notify(UpdateReasons.SHAPE_ACTIVATED); } - public rotate(rotation: Rotation, remember: boolean = false): void { - if (rotation === Rotation.CLOCKWISE90) { - this.data.angle += 90; - } else { - this.data.angle -= 90; + public rotate(rotationAngle: number): void { + if (this.data.angle !== rotationAngle) { + this.data.angle = (360 + Math.floor((rotationAngle) / 90) * 90) % 360; + this.fit(); } - - this.data.angle %= 360; - this.data.rememberAngle = remember; - this.fit(); } public focus(clientID: number, padding: number): void { diff --git a/cvat-ui/src/actions/annotation-actions.ts b/cvat-ui/src/actions/annotation-actions.ts index 62c95586afc4..bc49fb02cd79 100644 --- a/cvat-ui/src/actions/annotation-actions.ts +++ b/cvat-ui/src/actions/annotation-actions.ts @@ -17,6 +17,7 @@ import { ObjectType, Task, FrameSpeed, + Rotation, } from 'reducers/interfaces'; import getCore from 'cvat-core'; @@ -129,6 +130,7 @@ export enum AnnotationActionTypes { CHANGE_ANNOTATIONS_FILTERS = 'CHANGE_ANNOTATIONS_FILTERS', FETCH_ANNOTATIONS_SUCCESS = 'FETCH_ANNOTATIONS_SUCCESS', FETCH_ANNOTATIONS_FAILED = 'FETCH_ANNOTATIONS_FAILED', + ROTATE_FRAME = 'ROTATE_FRAME', SWITCH_Z_LAYER = 'SWITCH_Z_LAYER', ADD_Z_LAYER = 'ADD_Z_LAYER', } @@ -666,6 +668,27 @@ ThunkAction, {}, {}, AnyAction> { }; } + +export function rotateCurrentFrame(rotation: Rotation): AnyAction { + const state: CombinedState = getStore().getState(); + const { number: frameNumber } = state.annotation.player.frame; + const { startFrame } = state.annotation.job.instance; + const { frameAngles } = state.annotation.player; + const { rotateAll } = state.settings.player; + + const frameAngle = (frameAngles[frameNumber - startFrame] + + (rotation === Rotation.CLOCKWISE90 ? 90 : 270)) % 360; + + return { + type: AnnotationActionTypes.ROTATE_FRAME, + payload: { + offset: frameNumber - state.annotation.job.instance.startFrame, + angle: frameAngle, + rotateAll, + }, + }; +} + export function dragCanvas(enabled: boolean): AnyAction { return { type: AnnotationActionTypes.DRAG_CANVAS, diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/canvas-wrapper.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/canvas-wrapper.tsx index d95b901a0d25..32088df5de53 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/canvas-wrapper.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/canvas-wrapper.tsx @@ -37,6 +37,7 @@ interface Props { selectedStatesID: number[]; annotations: any[]; frameData: any; + frameAngle: number; frame: number; opacity: number; colorBy: ColorBy; @@ -101,6 +102,7 @@ export default class CanvasWrapperComponent extends React.PureComponent { selectedOpacity, blackBorders, frameData, + frameAngle, annotations, canvasInstance, sidebarCollapsed, @@ -149,6 +151,10 @@ export default class CanvasWrapperComponent extends React.PureComponent { canvasInstance.setZLayer(curZLayer); } + if (prevProps.frameAngle !== frameAngle) { + canvasInstance.rotate(frameAngle); + } + this.activateOnCanvas(); } @@ -305,11 +311,13 @@ export default class CanvasWrapperComponent extends React.PureComponent { const { annotations, frameData, + frameAngle, canvasInstance, } = this.props; if (frameData !== null) { canvasInstance.setup(frameData, annotations); + canvasInstance.rotate(frameAngle); } } diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/controls-side-bar.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/controls-side-bar.tsx index 5038a9ecad7a..176c384b44bb 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/controls-side-bar.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/controls-side-bar.tsx @@ -12,6 +12,7 @@ import { import { ActiveControl, + Rotation } from 'reducers/interfaces'; import { @@ -22,9 +23,9 @@ import { Canvas, } from 'cvat-canvas'; +import RotateControl from './rotate-control'; import CursorControl from './cursor-control'; import MoveControl from './move-control'; -import RotateControl from './rotate-control'; import FitControl from './fit-control'; import ResizeControl from './resize-control'; import DrawRectangleControl from './draw-rectangle-control'; @@ -37,23 +38,23 @@ import SplitControl from './split-control'; interface Props { canvasInstance: Canvas; - rotateAll: boolean; activeControl: ActiveControl; mergeObjects(enabled: boolean): void; groupObjects(enabled: boolean): void; splitTrack(enabled: boolean): void; + rotateFrame(rotation: Rotation): void; } export default function ControlsSideBarComponent(props: Props): JSX.Element { const { canvasInstance, activeControl, - rotateAll, mergeObjects, groupObjects, splitTrack, + rotateFrame, } = props; return ( @@ -64,7 +65,7 @@ export default function ControlsSideBarComponent(props: Props): JSX.Element { > - +
diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/rotate-control.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/rotate-control.tsx index 77b826bda915..139bff153326 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/rotate-control.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/rotate-control.tsx @@ -16,18 +16,15 @@ import { import { Rotation, - Canvas, -} from 'cvat-canvas'; +} from 'reducers/interfaces'; interface Props { - canvasInstance: Canvas; - rotateAll: boolean; + rotateFrame(rotation: Rotation): void; } function RotateControl(props: Props): JSX.Element { const { - rotateAll, - canvasInstance, + rotateFrame, } = props; return ( @@ -39,16 +36,14 @@ function RotateControl(props: Props): JSX.Element { canvasInstance - .rotate(Rotation.ANTICLOCKWISE90, rotateAll)} + onClick={(): void => rotateFrame(Rotation.ANTICLOCKWISE90)} component={RotateIcon} /> canvasInstance - .rotate(Rotation.CLOCKWISE90, rotateAll)} + onClick={(): void => rotateFrame(Rotation.CLOCKWISE90)} component={RotateIcon} /> diff --git a/cvat-ui/src/containers/annotation-page/standard-workspace/canvas-wrapper.tsx b/cvat-ui/src/containers/annotation-page/standard-workspace/canvas-wrapper.tsx index 93d2a8b15b24..f2185a6d4e2a 100644 --- a/cvat-ui/src/containers/annotation-page/standard-workspace/canvas-wrapper.tsx +++ b/cvat-ui/src/containers/annotation-page/standard-workspace/canvas-wrapper.tsx @@ -45,6 +45,7 @@ interface StateToProps { selectedStatesID: number[]; annotations: any[]; frameData: any; + frameAngle: number; frame: number; opacity: number; colorBy: ColorBy; @@ -105,6 +106,7 @@ function mapStateToProps(state: CombinedState): StateToProps { data: frameData, number: frame, }, + frameAngles, }, annotations: { states: annotations, @@ -143,6 +145,7 @@ function mapStateToProps(state: CombinedState): StateToProps { canvasInstance, jobInstance, frameData, + frameAngle: frameAngles[frame - jobInstance.startFrame], frame, activatedStateID, selectedStatesID, diff --git a/cvat-ui/src/containers/annotation-page/standard-workspace/controls-side-bar/controls-side-bar.tsx b/cvat-ui/src/containers/annotation-page/standard-workspace/controls-side-bar/controls-side-bar.tsx index 2e614b0f43c2..177647892f49 100644 --- a/cvat-ui/src/containers/annotation-page/standard-workspace/controls-side-bar/controls-side-bar.tsx +++ b/cvat-ui/src/containers/annotation-page/standard-workspace/controls-side-bar/controls-side-bar.tsx @@ -11,11 +11,13 @@ import { mergeObjects, groupObjects, splitTrack, + rotateCurrentFrame, } from 'actions/annotation-actions'; import ControlsSideBarComponent from 'components/annotation-page/standard-workspace/controls-side-bar/controls-side-bar'; import { ActiveControl, CombinedState, + Rotation, } from 'reducers/interfaces'; interface StateToProps { @@ -28,6 +30,7 @@ interface DispatchToProps { mergeObjects(enabled: boolean): void; groupObjects(enabled: boolean): void; splitTrack(enabled: boolean): void; + rotateFrame(angle: Rotation): void; } function mapStateToProps(state: CombinedState): StateToProps { @@ -63,6 +66,9 @@ function dispatchToProps(dispatch: any): DispatchToProps { splitTrack(enabled: boolean): void { dispatch(splitTrack(enabled)); }, + rotateFrame(rotation: Rotation): void { + dispatch(rotateCurrentFrame(rotation)); + }, }; } diff --git a/cvat-ui/src/cvat-canvas.ts b/cvat-ui/src/cvat-canvas.ts index 8ca0cd28ed41..874637adeeb7 100644 --- a/cvat-ui/src/cvat-canvas.ts +++ b/cvat-ui/src/cvat-canvas.ts @@ -4,14 +4,12 @@ import { Canvas, - Rotation, CanvasVersion, RectDrawingMethod, } from '../../cvat-canvas/src/typescript/canvas'; export { Canvas, - Rotation, CanvasVersion, RectDrawingMethod, }; diff --git a/cvat-ui/src/reducers/annotation-reducer.ts b/cvat-ui/src/reducers/annotation-reducer.ts index e760a4d5643a..b9615fe70be3 100644 --- a/cvat-ui/src/reducers/annotation-reducer.ts +++ b/cvat-ui/src/reducers/annotation-reducer.ts @@ -44,6 +44,7 @@ const defaultState: AnnotationState = { changeTime: null, }, playing: false, + frameAngles: [], }, drawing: { activeShapeType: ShapeType.RECTANGLE, @@ -138,6 +139,7 @@ export default (state = defaultState, action: AnyAction): AnnotationState => { number, data, }, + frameAngles: Array(job.stopFrame - job.startFrame + 1).fill(0), }, drawing: { ...state.drawing, @@ -233,6 +235,17 @@ export default (state = defaultState, action: AnyAction): AnnotationState => { }, }; } + case AnnotationActionTypes.ROTATE_FRAME: { + const { offset, angle, rotateAll } = action.payload; + return { + ...state, + player: { + ...state.player, + frameAngles: state.player.frameAngles.map((_angle: number, idx: number) => ( + rotateAll || offset === idx ? angle : _angle)), + }, + }; + } case AnnotationActionTypes.SAVE_ANNOTATIONS: { return { ...state, diff --git a/cvat-ui/src/reducers/interfaces.ts b/cvat-ui/src/reducers/interfaces.ts index caf814b587f3..644d85d50c8c 100644 --- a/cvat-ui/src/reducers/interfaces.ts +++ b/cvat-ui/src/reducers/interfaces.ts @@ -275,6 +275,11 @@ export enum ContextMenuType { CANVAS_SHAPE = 'canvas_shape', } +export enum Rotation { + ANTICLOCKWISE90, + CLOCKWISE90, +} + export interface AnnotationState { activities: { loads: { @@ -308,6 +313,7 @@ export interface AnnotationState { changeTime: number | null; }; playing: boolean; + frameAngles: number[]; }; drawing: { activeShapeType: ShapeType;