diff --git a/cvat-canvas/src/typescript/canvasModel.ts b/cvat-canvas/src/typescript/canvasModel.ts index cc3be054a0b..58e22952d25 100644 --- a/cvat-canvas/src/typescript/canvasModel.ts +++ b/cvat-canvas/src/typescript/canvasModel.ts @@ -110,7 +110,7 @@ export enum Mode { } export interface CanvasModel { - readonly image: string; + readonly image: HTMLImageElement | null; readonly objects: any[]; readonly gridSize: Size; readonly focusData: FocusData; @@ -151,7 +151,7 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel { activeElement: ActiveElement; angle: number; canvasSize: Size; - image: string; + image: HTMLImageElement | null; imageID: number | null; imageOffset: number; imageSize: Size; @@ -183,7 +183,7 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel { height: 0, width: 0, }, - image: '', + image: null, imageID: null, imageOffset: 0, imageSize: { @@ -300,10 +300,10 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel { this.data.imageID = frameData.number; frameData.data( (): void => { - this.data.image = ''; + this.data.image = null; this.notify(UpdateReasons.IMAGE_CHANGED); }, - ).then((data: string): void => { + ).then((data: HTMLImageElement): void => { if (frameData.number !== this.data.imageID) { // already another image return; @@ -514,7 +514,7 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel { )); } - public get image(): string { + public get image(): HTMLImageElement | null { return this.data.image; } diff --git a/cvat-canvas/src/typescript/canvasView.ts b/cvat-canvas/src/typescript/canvasView.ts index 8fd1d01f92a..98efb21e993 100644 --- a/cvat-canvas/src/typescript/canvasView.ts +++ b/cvat-canvas/src/typescript/canvasView.ts @@ -21,7 +21,6 @@ import consts from './consts'; import { translateToSVG, translateFromSVG, - translateBetweenSVG, pointsToArray, displayShapeSize, ShapeSizeElement, @@ -48,7 +47,7 @@ export class CanvasViewImpl implements CanvasView, Listener { private loadingAnimation: SVGSVGElement; private text: SVGSVGElement; private adoptedText: SVG.Container; - private background: SVGSVGElement; + private background: HTMLCanvasElement; private grid: SVGSVGElement; private content: SVGSVGElement; private adoptedContent: SVG.Container; @@ -220,13 +219,14 @@ export class CanvasViewImpl implements CanvasView, Listener { private onFindObject(e: MouseEvent): void { if (e.which === 1 || e.which === 0) { - const [x, y] = translateToSVG(this.background, [e.clientX, e.clientY]); + const { offset } = this.controller.geometry; + const [x, y] = translateToSVG(this.content, [e.clientX, e.clientY]); const event: CustomEvent = new CustomEvent('canvas.find', { bubbles: false, cancelable: true, detail: { - x, - y, + x: x - offset, + y: y - offset, states: this.controller.objects, }, }); @@ -365,26 +365,9 @@ export class CanvasViewImpl implements CanvasView, Listener { } private setupObjects(states: any[]): void { - const backgroundMatrix = this.background.getScreenCTM(); - const contentMatrix = (this.content.getScreenCTM() as DOMMatrix).inverse(); - - const translate = (points: number[]): number[] => { - if (backgroundMatrix && contentMatrix) { - const matrix = (contentMatrix as DOMMatrix).multiply(backgroundMatrix); - return points.reduce((result: number[], _: number, idx: number): number[] => { - if (idx % 2) { - let p = (this.background as SVGSVGElement).createSVGPoint(); - p.x = points[idx - 1]; - p.y = points[idx]; - p = p.matrixTransform(matrix); - result.push(p.x, p.y); - } - return result; - }, []); - } - - return points; - }; + const { offset } = this.controller.geometry; + const translate = (points: number[]): number[] => points + .map((coord: number): number => coord + offset); const created = []; const updated = []; @@ -524,7 +507,8 @@ export class CanvasViewImpl implements CanvasView, Listener { .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.createElement('canvas'); + // 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'); @@ -593,12 +577,10 @@ export class CanvasViewImpl implements CanvasView, Listener { this.onDrawDone.bind(this), this.adoptedContent, this.adoptedText, - this.background, ); this.editHandler = new EditHandlerImpl( this.onEditDone.bind(this), this.adoptedContent, - this.background, ); this.mergeHandler = new MergeHandlerImpl( this.onMergeDone.bind(this), @@ -646,8 +628,9 @@ export class CanvasViewImpl implements CanvasView, Listener { }); this.content.addEventListener('wheel', (event): void => { - const point = translateToSVG(self.background, [event.clientX, event.clientY]); - self.controller.zoom(point[0], point[1], event.deltaY > 0 ? -1 : 1); + const { offset } = this.controller.geometry; + const point = translateToSVG(this.content, [event.clientX, event.clientY]); + self.controller.zoom(point[0] - offset, point[1] - offset, event.deltaY > 0 ? -1 : 1); this.canvas.dispatchEvent(new CustomEvent('canvas.zoom', { bubbles: false, cancelable: true, @@ -661,13 +644,14 @@ export class CanvasViewImpl implements CanvasView, Listener { if (this.mode !== Mode.IDLE) return; if (e.ctrlKey || e.shiftKey) return; - const [x, y] = translateToSVG(this.background, [e.clientX, e.clientY]); + const { offset } = this.controller.geometry; + const [x, y] = translateToSVG(this.content, [e.clientX, e.clientY]); const event: CustomEvent = new CustomEvent('canvas.moved', { bubbles: false, cancelable: true, detail: { - x, - y, + x: x - offset, + y: y - offset, states: this.controller.objects, }, }); @@ -682,11 +666,17 @@ export class CanvasViewImpl implements CanvasView, Listener { public notify(model: CanvasModel & Master, reason: UpdateReasons): void { this.geometry = this.controller.geometry; if (reason === UpdateReasons.IMAGE_CHANGED) { - if (!model.image.length) { + const { image } = model; + if (!image) { this.loadingAnimation.classList.remove('cvat_canvas_hidden'); } else { this.loadingAnimation.classList.add('cvat_canvas_hidden'); - this.background.style.backgroundImage = `url("${model.image}")`; + const ctx = this.background.getContext('2d'); + this.background.setAttribute('width', `${image.width}px`); + this.background.setAttribute('height', `${image.height}px`); + if (ctx) { + ctx.drawImage(image, 0, 0); + } this.moveCanvas(); this.resizeCanvas(); this.transformCanvas(); @@ -1058,14 +1048,15 @@ export class CanvasViewImpl implements CanvasView, Listener { const p1 = e.detail.handler.startPoints.point; const p2 = e.detail.p; const delta = 1; + const { offset } = this.controller.geometry; if (Math.sqrt(((p1.x - p2.x) ** 2) + ((p1.y - p2.y) ** 2)) >= delta) { const points = pointsToArray( shape.attr('points') || `${shape.attr('x')},${shape.attr('y')} ` + `${shape.attr('x') + shape.attr('width')},` + `${shape.attr('y') + shape.attr('height')}`, - ); + ).map((x: number): number => x - offset); - this.onEditDone(state, translateBetweenSVG(this.content, this.background, points)); + this.onEditDone(state, points); } }); @@ -1105,13 +1096,15 @@ export class CanvasViewImpl implements CanvasView, Listener { this.mode = Mode.IDLE; if (resized) { + const { offset } = this.controller.geometry; + const points = pointsToArray( shape.attr('points') || `${shape.attr('x')},${shape.attr('y')} ` + `${shape.attr('x') + shape.attr('width')},` + `${shape.attr('y') + shape.attr('height')}`, - ); + ).map((x: number): number => x - offset); - this.onEditDone(state, translateBetweenSVG(this.content, this.background, points)); + this.onEditDone(state, points); } }); diff --git a/cvat-canvas/src/typescript/drawHandler.ts b/cvat-canvas/src/typescript/drawHandler.ts index da0798faae7..ed27cf7c370 100644 --- a/cvat-canvas/src/typescript/drawHandler.ts +++ b/cvat-canvas/src/typescript/drawHandler.ts @@ -15,7 +15,6 @@ import { import { translateToSVG, - translateBetweenSVG, displayShapeSize, ShapeSizeElement, pointsToString, @@ -35,7 +34,6 @@ export class DrawHandlerImpl implements DrawHandler { private onDrawDone: (data: object, continueDraw?: boolean) => void; private canvas: SVG.Container; private text: SVG.Container; - private background: SVGSVGElement; private crosshair: { x: SVG.Line; y: SVG.Line; @@ -52,12 +50,10 @@ export class DrawHandlerImpl implements DrawHandler { private getFinalRectCoordinates(bbox: BBox): number[] { const frameWidth = this.geometry.image.width; const frameHeight = this.geometry.image.height; + const { offset } = this.geometry; - let [xtl, ytl, xbr, ybr] = translateBetweenSVG( - this.canvas.node as any as SVGSVGElement, - this.background, - [bbox.x, bbox.y, bbox.x + bbox.width, bbox.y + bbox.height], - ); + let [xtl, ytl, xbr, ybr] = [bbox.x, bbox.y, bbox.x + bbox.width, bbox.y + bbox.height] + .map((coord: number): number => coord - offset); xtl = Math.min(Math.max(xtl, 0), frameWidth); xbr = Math.min(Math.max(xbr, 0), frameWidth); @@ -71,12 +67,8 @@ export class DrawHandlerImpl implements DrawHandler { points: number[]; box: Box; } { - const points = translateBetweenSVG( - this.canvas.node as any as SVGSVGElement, - this.background, - targetPoints, - ); - + const { offset } = this.geometry; + const points = targetPoints.map((coord: number): number => coord - offset); const box = { xtl: Number.MAX_SAFE_INTEGER, ytl: Number.MAX_SAFE_INTEGER, @@ -474,12 +466,10 @@ export class DrawHandlerImpl implements DrawHandler { private startDraw(): void { // TODO: Use enums after typification cvat-core if (this.drawData.initialState) { + const { offset } = this.geometry; if (this.drawData.shapeType === 'rectangle') { - const [xtl, ytl, xbr, ybr] = translateBetweenSVG( - this.background, - this.canvas.node as any as SVGSVGElement, - this.drawData.initialState.points, - ); + const [xtl, ytl, xbr, ybr] = this.drawData.initialState.points + .map((coord: number): number => coord + offset); this.pasteBox({ x: xtl, @@ -488,12 +478,8 @@ export class DrawHandlerImpl implements DrawHandler { height: ybr - ytl, }); } else { - const points = translateBetweenSVG( - this.background, - this.canvas.node as any as SVGSVGElement, - this.drawData.initialState.points, - ); - + const points = this.drawData.initialState.points + .map((coord: number): number => coord + offset); const stringifiedPoints = pointsToString(points); if (this.drawData.shapeType === 'polygon') { @@ -521,12 +507,10 @@ export class DrawHandlerImpl implements DrawHandler { onDrawDone: (data: object, continueDraw?: boolean) => void, canvas: SVG.Container, text: SVG.Container, - background: SVGSVGElement, ) { this.onDrawDone = onDrawDone; this.canvas = canvas; this.text = text; - this.background = background; this.drawData = null; this.geometry = null; this.crosshair = null; diff --git a/cvat-canvas/src/typescript/editHandler.ts b/cvat-canvas/src/typescript/editHandler.ts index bc0dc82309a..8daf9055f16 100644 --- a/cvat-canvas/src/typescript/editHandler.ts +++ b/cvat-canvas/src/typescript/editHandler.ts @@ -9,7 +9,6 @@ import 'svg.select.js'; import consts from './consts'; import { translateFromSVG, - translateBetweenSVG, pointsToArray, } from './shared'; import { @@ -27,7 +26,6 @@ export class EditHandlerImpl implements EditHandler { private onEditDone: (state: any, points: number[]) => void; private geometry: Geometry; private canvas: SVG.Container; - private background: SVGSVGElement; private editData: EditData; private editedShape: SVG.Shape; private editLine: SVG.PolyLine; @@ -94,21 +92,19 @@ export class EditHandlerImpl implements EditHandler { } } - private stopEdit(e: MouseEvent): void { - function selectPolygon(shape: SVG.Polygon): void { - const points = translateBetweenSVG( - this.canvas.node as any as SVGSVGElement, - this.background, - pointsToArray(shape.attr('points')), - ); - - const { state } = this.editData; - this.edit({ - enabled: false, - }); - this.onEditDone(state, points); - } + private selectPolygon(shape: SVG.Polygon): void { + const { offset } = this.geometry; + const points = pointsToArray(shape.attr('points')) + .map((coord: number): number => coord - offset); + const { state } = this.editData; + this.edit({ + enabled: false, + }); + this.onEditDone(state, points); + } + + private stopEdit(e: MouseEvent): void { if (!this.editLine) { return; } @@ -154,7 +150,7 @@ export class EditHandlerImpl implements EditHandler { } for (const clone of this.clones) { - clone.on('click', selectPolygon.bind(this, clone)); + clone.on('click', (): void => this.selectPolygon(clone)); clone.on('mouseenter', (): void => { clone.addClass('cvat_canvas_shape_splitting'); }).on('mouseleave', (): void => { @@ -170,6 +166,7 @@ export class EditHandlerImpl implements EditHandler { } let points = null; + const { offset } = this.geometry; if (this.editData.state.shapeType === 'polyline') { if (start !== this.editData.pointID) { linePoints.reverse(); @@ -181,11 +178,8 @@ export class EditHandlerImpl implements EditHandler { points = oldPoints.concat(linePoints.slice(0, -1)); } - points = translateBetweenSVG( - this.canvas.node as any as SVGSVGElement, - this.background, - pointsToArray(points.join(' ')), - ); + points = pointsToArray(points.join(' ')) + .map((coord: number): number => coord - offset); const { state } = this.editData; this.edit({ @@ -284,11 +278,9 @@ export class EditHandlerImpl implements EditHandler { public constructor( onEditDone: (state: any, points: number[]) => void, canvas: SVG.Container, - background: SVGSVGElement, ) { this.onEditDone = onEditDone; this.canvas = canvas; - this.background = background; this.editData = null; this.editedShape = null; this.editLine = null; diff --git a/cvat-canvas/src/typescript/shared.ts b/cvat-canvas/src/typescript/shared.ts index d98eaaad739..f89ed7eb96d 100644 --- a/cvat-canvas/src/typescript/shared.ts +++ b/cvat-canvas/src/typescript/shared.ts @@ -58,16 +58,6 @@ export function translateToSVG(svg: SVGSVGElement, points: number[]): number[] { return output; } -// Translate point array from the first canvas coordinate system -// to another -export function translateBetweenSVG( - from: SVGSVGElement, - to: SVGSVGElement, - points: number[], -): number[] { - return translateToSVG(to, translateFromSVG(from, points)); -} - export function pointsToString(points: number[]): string { return points.reduce((acc, val, idx): string => { if (idx % 2) { diff --git a/cvat-core/src/frames.js b/cvat-core/src/frames.js index 1ab2eaf21a9..4141de07bfc 100644 --- a/cvat-core/src/frames.js +++ b/cvat-core/src/frames.js @@ -100,9 +100,14 @@ } else if (isBrowser) { const reader = new FileReader(); reader.onload = () => { - frameCache[this.tid][this.number] = reader.result; - resolve(frameCache[this.tid][this.number]); + const image = new Image(frame.width, frame.height); + image.onload = () => { + frameCache[this.tid][this.number] = image; + resolve(frameCache[this.tid][this.number]); + }; + image.src = reader.result; }; + reader.readAsDataURL(frame); } }