diff --git a/client/src/components/annotators/ImageAnnotator.vue b/client/src/components/annotators/ImageAnnotator.vue index 3bc8f702c..c6bb9394d 100644 --- a/client/src/components/annotators/ImageAnnotator.vue +++ b/client/src/components/annotators/ImageAnnotator.vue @@ -54,19 +54,10 @@ export default Vue.extend({ this.quadFeatureLayer = this.geoViewer.createLayer('feature', { features: ['quad'], }); - this.quadFeature = this.quadFeatureLayer - .createFeature('quad') - .data([ - { - ul: { x: 0, y: 0 }, - lr: { x: this.width, y: this.height }, - image: this.imgs[this.frame], - }, - ]) - .draw(); + this.quadFeature = this.quadFeatureLayer.createFeature('quad'); + this.drawImage(this.imgs[this.frame]); this.ready = true; }, - async play() { try { this.playing = true; @@ -75,24 +66,43 @@ export default Vue.extend({ console.error(ex); } }, - - async seek(frame) { - this.lastFrame = this.frame; - this.frame = frame; - this.syncedFrame = frame; - this.emitFrame(); - this.cacheImages(); + drawImage(img) { + if ((img.naturalWidth !== this.width) || (img.naturalHeight !== this.height)) { + this.width = img.naturalWidth; + this.height = img.naturalHeight; + this.resetMapDimensions(); + } this.quadFeature .data([ { ul: { x: 0, y: 0 }, lr: { x: this.width, y: this.height }, - image: this.imgs[frame], + image: img, }, ]) .draw(); }, - + async seek(newFrame) { + this.lastFrame = this.frame; + this.frame = newFrame; + this.syncedFrame = newFrame; + this.emitFrame(); + const imgsPromise = this.cacheImages(); + const img = this.imgs[newFrame]; + if (img.cached) { + // if image is already loaded, draw it now + this.drawImage(img); + } else { + // else wait for it to load + const imgs = await imgsPromise; + const loadedImg = await imgs[newFrame]; + if (loadedImg !== undefined && loadedImg.frame === this.frame) { + // if the image exists () + // and the seek hasn't changed since the image completed loading, draw it. + this.drawImage(loadedImg); + } + } + }, pause() { this.playing = false; this.loadingVideo = false; @@ -153,7 +163,7 @@ export default Vue.extend({ await this.loadFrame(frame); } // Cache a new range of images based on current frame - this.cacheNewRange(min, max); + return this.cacheNewRange(min, max); }, /** * Wraps loading of a single frame in a promise, used to gurantee frame loads @@ -169,6 +179,7 @@ export default Vue.extend({ img.onload = () => { img.onload = null; img.cached = true; + img.frame = frame; resolve(frame); }; this.loadImageFunc(this.imageData[frame], img); @@ -181,40 +192,49 @@ export default Vue.extend({ * @param {int} max upper bound frame number for caching */ cacheNewRange(min, max) { + const frameImages = []; for (let i = this.frame; i <= max; i += 1) { - this.cacheFrame(i); + frameImages[i] = this.cacheFrame(i); const minusFrame = this.frame - (i - this.frame); if (minusFrame >= min) { this.cacheFrame(minusFrame); } } + return frameImages; }, /** * Adds a single frame to the pendingImgs array for loading and assigns it to the master * imgs list. Once the image is loaded it is removed from the pendingImgs * @param {int} i the image to cache if it isn't already assigned + * @returns {Array>>} loaded images */ cacheFrame(i) { + let p = Promise.resolve(this.imgs[i]); if (!this.imgs[i]) { const img = new Image(); img.crossOrigin = 'Anonymous'; this.imgs[i] = img; const imageAndFrame = [img, i]; this.pendingImgs.add(imageAndFrame); - img.onload = () => { - this.pendingImgs.delete(imageAndFrame); - img.onload = null; - img.cached = true; - // If we are trying to play and waiting for loaded frames we check the cache again - if (this.playing && this.loadingVideo) { - if (this.checkCached(this.playCache)) { - this.loadingVideo = false; - this.syncWithVideo(); + p = new Promise((resolve) => { + img.onload = () => { + this.pendingImgs.delete(imageAndFrame); + img.onload = null; + img.cached = true; + img.frame = i; + // If we are trying to play and waiting for loaded frames we check the cache again + if (this.playing && this.loadingVideo) { + if (this.checkCached(this.playCache)) { + this.loadingVideo = false; + this.syncWithVideo(); + } } - } - }; + resolve(img); + }; + }); this.loadImageFunc(this.imageData[i], img); } + return p; }, /** * Checks to see if there is enough cached images to play for X seconds diff --git a/client/src/components/annotators/annotator.js b/client/src/components/annotators/annotator.js index cba312f7a..6c5a3e1f4 100644 --- a/client/src/components/annotators/annotator.js +++ b/client/src/components/annotators/annotator.js @@ -72,6 +72,30 @@ export default { setImageCursor(newCursor) { this.imageCursor = `${newCursor}`; }, + resetMapDimensions() { + this.geoViewer.bounds({ + left: 0, + top: 0, + bottom: this.height, + right: this.width, + }); + const params = geo.util.pixelCoordinateParams( + this.$refs.container, + this.width, + this.height, + this.width, + this.height, + ); + this.geoViewer.maxBounds({ + left: 0, + top: 0, + right: params.map.maxBounds.right, + bottom: params.map.maxBounds.bottom, + }); + this.geoViewer.zoomRange(params.map); + this.geoViewer.zoom(this.geoViewer.zoomRange().origMin); + this.geoViewer.center({ x: 0, y: 0 }); + }, baseInit() { const params = geo.util.pixelCoordinateParams( this.$refs.container, @@ -81,6 +105,7 @@ export default { this.height, ); this.geoViewer = geo.map(params.map); + this.resetMapDimensions(); this.geoViewer.zoomRange({ min: this.geoViewer.zoomRange().origMin, max: this.geoViewer.zoomRange().max + 3, @@ -117,6 +142,7 @@ export default { interactorOpts.wheelScaleY = 0.2; this.geoViewer.interactor().options(interactorOpts); }, + prevFrame() { const targetFrame = this.frame - 1; if (targetFrame >= 0) {