From ab6af04e5bdf91ffeb2817404a26209c2350aed5 Mon Sep 17 00:00:00 2001 From: Justin Georgi Date: Wed, 25 Sep 2024 15:05:00 +0000 Subject: [PATCH] Use canvas for image rendering (#197) Closes #194, Closes #189 Instead of making the detection image the background of the canvas element the image is now drawn as part of the canvas. This enables pan and zoom of the image as well. Reviewed-on: https://gitea.azgeorgis.net/ALVINN/ALVINN_f7/pulls/197 --- src/components/svg-icon.vue | 6 +- src/pages/detect.vue | 158 +++++++++++++++++++++++++++--------- src/pages/touch-mixin.js | 53 ++++++++++++ 3 files changed, 179 insertions(+), 38 deletions(-) create mode 100644 src/pages/touch-mixin.js diff --git a/src/components/svg-icon.vue b/src/components/svg-icon.vue index 5e64430..e3b27ff 100644 --- a/src/components/svg-icon.vue +++ b/src/components/svg-icon.vue @@ -17,6 +17,8 @@ + + @@ -44,7 +46,9 @@ 'limbs', 'head', 'photo_sample', - 'reset_slide' + 'reset_slide', + 'zoom_to', + 'reset_zoom' ] return iconList.includes(value) } diff --git a/src/pages/detect.vue b/src/pages/detect.vue index 243077d..06882f6 100644 --- a/src/pages/detect.vue +++ b/src/pages/detect.vue @@ -9,15 +9,28 @@ +
Capture
- +
- - - - + + + + + + + {{ showResults.length - numResults }} @@ -93,23 +109,6 @@ - - - - - - - - - - - - - - - - - @@ -139,11 +138,12 @@ import submitMixin from './submit-mixin' import detectionMixin from './detection-mixin' import cameraMixin from './camera-mixin' + import touchMixin from './touch-mixin' import detectionWorker from '@/assets/detect-worker.js?worker&inline' export default { - mixins: [submitMixin, detectionMixin, cameraMixin], + mixins: [submitMixin, detectionMixin, cameraMixin, touchMixin], props: { f7route: Object, }, @@ -160,6 +160,7 @@ classesList: [], imageLoaded: false, imageView: new Image(), + imCvsLocation: {}, imageLoadMode: "environment", detecting: false, detectPanel: false, @@ -181,7 +182,12 @@ cameraStream: null, infoLinkPos: {}, detectWorker: null, - vidWorker: null + vidWorker: null, + canvasMoving: false, + canvasOffset: {x: 0, y: 0}, + canvasZoom: 1, + structureZoomed: false, + debugInfo: null } }, setup() { @@ -334,6 +340,9 @@ self.reloadModel = false loadSuccess() } + f7.utils.nextFrame(() => { + this.selectChip("redraw") + }) } } @@ -375,6 +384,9 @@ f7.dialog.alert(`ALVINN structure finding error: ${e.message}`) }) } + f7.utils.nextFrame(() => { + this.selectChip("redraw") + }) }, selectAll (ev) { if (ev.target.checked) { @@ -446,10 +458,8 @@ let boxTop = boxCoords.cvsTop let boxWidth = boxCoords.cvsRight - boxCoords.cvsLeft let boxHeight = boxCoords.cvsBottom - boxCoords.cvsTop - this.infoLinkPos.x = boxCoords.cvsLeft - this.infoLinkPos.y = boxCoords.cvsTop - let boxMin = Math.min(boxHeight, boxWidth) - this.infoLinkPos.adj = (boxMin >= 50) ? 0 : Math.min(10, 50 - boxMin) + this.infoLinkPos.x = Math.min(Math.max(boxCoords.cvsLeft * this.canvasZoom + this.canvasOffset.x, 0),imCanvas.width) + this.infoLinkPos.y = Math.min(Math.max(boxCoords.cvsTop * this.canvasZoom + this.canvasOffset.y, 0), imCanvas.height) imageCtx.strokeRect(boxLeft, boxTop, boxWidth, boxHeight) this.selectedChip = iChip @@ -473,9 +483,20 @@ imCanvas.width = imCanvas.clientWidth imCanvas.height = imCanvas.clientHeight imageCtx.clearRect(0,0,imCanvas.width,imCanvas.height) + imageCtx.translate(this.canvasOffset.x,this.canvasOffset.y) + imageCtx.scale(this.canvasZoom,this.canvasZoom) imageCtx.globalAlpha = 1 imageCtx.strokeStyle = 'yellow' - imageCtx.lineWidth = 3 + imageCtx.lineWidth = 3 / this.canvasZoom + if (this.imageLoaded) { + let imageLoc = this.box2cvs({top: 0,left: 0,right: 1,bottom: 1}) + this.imCvsLocation.top = imageLoc[0].cvsTop + this.imCvsLocation.left = imageLoc[0].cvsLeft + this.imCvsLocation.width = imageLoc[0].cvsRight - imageLoc[0].cvsLeft + this.imCvsLocation.height = imageLoc[0].cvsBottom - imageLoc[0].cvsTop + imageCtx.drawImage(this.imageView, 0, 0, this.imageView.width, this.imageView.height, this.imCvsLocation.left, this.imCvsLocation.top, this.imCvsLocation.width, this.imCvsLocation.height) + } + this.structureZoomed = false return [imCanvas, imageCtx] }, getImage (searchImage) { @@ -516,8 +537,18 @@ this.imageView.src = imgData return(this.imageView.decode()) }).then( () => { - const [imCanvas, _] = this.resetView() - imCanvas.style['background-image'] = `url(${this.imageView.src})` + this.canvasOffset = {x: 0, y: 0} + this.canvasZoom = 1 + const imCanvas = this.$refs.image_cvs + imCanvas.width = imCanvas.clientWidth + imCanvas.height = imCanvas.clientHeight + const imageCtx = imCanvas.getContext("2d") + let imageLoc = this.box2cvs({top: 0,left: 0,right: 1,bottom: 1}) + this.imCvsLocation.top = imageLoc[0].cvsTop + this.imCvsLocation.left = imageLoc[0].cvsLeft + this.imCvsLocation.width = imageLoc[0].cvsRight - imageLoc[0].cvsLeft + this.imCvsLocation.height = imageLoc[0].cvsBottom - imageLoc[0].cvsTop + imageCtx.drawImage(this.imageView, 0, 0, this.imageView.width, this.imageView.height, this.imCvsLocation.left, this.imCvsLocation.top, this.imCvsLocation.width, this.imCvsLocation.height) f7.utils.nextFrame(() => { this.setData() }) @@ -538,10 +569,12 @@ }, structureClick(e) { const boxCoords = this.box2cvs(this.showResults) - var findBox = boxCoords.findIndex( (r, i) => { return r.cvsLeft <= e.offsetX && - r.cvsRight >= e.offsetX && - r.cvsTop <= e.offsetY && - r.cvsBottom >= e.offsetY && + let clickX = (e.offsetX - this.canvasOffset.x) / this.canvasZoom + let clickY = (e.offsetY - this.canvasOffset.y) / this.canvasZoom + var findBox = boxCoords.findIndex( (r, i) => { return r.cvsLeft <= clickX && + r.cvsRight >= clickX && + r.cvsTop <= clickY && + r.cvsBottom >= clickY && this.resultData.detections[i].resultIndex > this.selectedChip && this.resultData.detections[i].aboveThreshold && this.resultData.detections[i].isSearched && @@ -552,7 +585,7 @@ box2cvs(boxInput) { if (!boxInput || boxInput.length == 0) return [] const boxList = boxInput.length ? boxInput : [boxInput] - const [imCanvas, imageCtx] = this.resetView() + const imCanvas = this.$refs.image_cvs var imgWidth var imgHeight const imgAspect = this.imageView.width / this.imageView.height @@ -573,6 +606,57 @@ } }) return cvsCoords + }, + toggleSettings() { + this.showDetectSettings = !this.showDetectSettings + f7.utils.nextFrame(() => { + this.selectChip("redraw") + }) + }, + startMove() { + this.canvasMoving = true + }, + endMove() { + this.canvasMoving = false + }, + makeMove(event) { + if (this.canvasMoving) { + this.canvasOffset.x += event.movementX + this.canvasOffset.y += event.movementY + this.selectChip("redraw") + } + }, + spinWheel(event) { + let zoomFactor + if (event.wheelDelta > 0) { + zoomFactor = 1.05 + } else if (event.wheelDelta < 0) { + zoomFactor = 1 / 1.05 + } + this.canvasZoom *= zoomFactor + this.canvasOffset.x = event.offsetX * (1 - zoomFactor) + this.canvasOffset.x * zoomFactor + this.canvasOffset.y = event.offsetY * (1 - zoomFactor) + this.canvasOffset.y * zoomFactor + this.selectChip("redraw") + }, + resetZoom() { + this.canvasZoom = 1 + this.canvasOffset.x = 0 + this.canvasOffset.y = 0 + this.selectChip("redraw") + }, + zoomToSelected() { + const imCanvas = this.$refs.image_cvs + const boxCoords = this.box2cvs(this.resultData.detections[this.selectedChip])[0] + const boxWidth = boxCoords.cvsRight - boxCoords.cvsLeft + const boxHeight = boxCoords.cvsBottom - boxCoords.cvsTop + const boxMidX = (boxCoords.cvsRight + boxCoords.cvsLeft ) / 2 + const boxMidY = (boxCoords.cvsBottom + boxCoords.cvsTop ) / 2 + const zoomFactor = Math.min(imCanvas.width / boxWidth * .9, imCanvas.height / boxHeight * .9, 8) + this.canvasZoom = zoomFactor + this.canvasOffset.x = -(boxMidX * zoomFactor) + imCanvas.width / 2 + this.canvasOffset.y = -(boxMidY * zoomFactor) + imCanvas.height / 2 + this.selectChip("redraw") + this.structureZoomed = true } } } diff --git a/src/pages/touch-mixin.js b/src/pages/touch-mixin.js new file mode 100644 index 0000000..4c4981c --- /dev/null +++ b/src/pages/touch-mixin.js @@ -0,0 +1,53 @@ +export default { + data () { + return { + touchPrevious: {} + } + }, + methods: { + startTouch(event) { + if (event.touches.length == 1) { + this.touchPrevious = {x: event.touches[0].clientX, y: event.touches[0].clientY} + } + if (event.touches.length == 2) { + let midX = (event.touches.item(0).clientX + event.touches.item(1).clientX) / 2 + let midY = (event.touches.item(0).clientY + event.touches.item(1).clientY) / 2 + this.touchPrevious = {distance: this.touchDistance(event.touches), x: midX, y: midY} + } + }, + endTouch(event) { + if (event.touches.length == 1) { + this.touchPrevious = {x: event.touches[0].clientX, y: event.touches[0].clientY} + } else { + //this.debugInfo = null + } + }, + moveTouch(event) { + switch (event.touches.length) { + case 1: + console.log(event) + this.canvasOffset.x += event.touches[0].clientX - this.touchPrevious.x + this.canvasOffset.y += event.touches[0].clientY - this.touchPrevious.y + this.touchPrevious = {x: event.touches[0].clientX, y: event.touches[0].clientY} + break; + case 2: + let newDistance = this.touchDistance(event.touches) + let midX = (event.touches.item(0).clientX + event.touches.item(1).clientX) / 2 + let midY = (event.touches.item(0).clientY + event.touches.item(1).clientY) / 2 + let scale = newDistance / this.touchPrevious.distance + let scaleChange = this.canvasZoom * (scale - 1) + this.canvasZoom *= scale + this.canvasOffset.x += -((midX - 16) * scaleChange) + (midX - this.touchPrevious.x) + this.canvasOffset.y += -((midY - 96) * scaleChange) + (midY - this.touchPrevious.y) + this.touchPrevious = {distance: newDistance, x: midX, y: midY} + break; + } + this.selectChip("redraw") + }, + touchDistance(touches) { + let touch1 = touches.item(0) + let touch2 = touches.item(1) + return Math.sqrt((touch1.clientX - touch2.clientX) ** 2 + (touch1.clientY - touch2.clientY) ** 2) + } + } +} \ No newline at end of file