Create classes for structures and coordinates
Signed-off-by: Justin Georgi <justin.georgi@gmail.com>
This commit is contained in:
157
src/js/structures.js
Normal file
157
src/js/structures.js
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
class Coordinate {
|
||||||
|
constructor(x, y) {
|
||||||
|
this.x = x
|
||||||
|
this.y = y
|
||||||
|
}
|
||||||
|
|
||||||
|
toRefFrame(...frameArgs) {
|
||||||
|
if (frameArgs.length == 0) {
|
||||||
|
return {x: this.x, y: this.y}
|
||||||
|
}
|
||||||
|
let outFrames = []
|
||||||
|
//Get Coordinates in Image Reference Frame
|
||||||
|
if (frameArgs[0].tagName == 'IMG' && frameArgs[0].width && frameArgs[0].height) {
|
||||||
|
outFrames.push({
|
||||||
|
x: this.x * frameArgs[0].width,
|
||||||
|
y: this.y * frameArgs[0].height
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
throw new Error('Coordinate: invalid reference frame for frameType: Image')
|
||||||
|
}
|
||||||
|
//Get Coordinates in Canvas Reference Frame
|
||||||
|
if (frameArgs[1]) {
|
||||||
|
if (frameArgs[1].tagName == 'CANVAS' && frameArgs[1].width && frameArgs[1].height) {
|
||||||
|
let imgWidth
|
||||||
|
let imgHeight
|
||||||
|
const imgAspect = frameArgs[0].width / frameArgs[0].height
|
||||||
|
const rendAspect = frameArgs[1].width / frameArgs[1].height
|
||||||
|
if (imgAspect >= rendAspect) {
|
||||||
|
imgWidth = frameArgs[1].width
|
||||||
|
imgHeight = frameArgs[1].width / imgAspect
|
||||||
|
} else {
|
||||||
|
imgWidth = frameArgs[1].height * imgAspect
|
||||||
|
imgHeight = frameArgs[1].height
|
||||||
|
}
|
||||||
|
outFrames.push({
|
||||||
|
x: (frameArgs[1].width - imgWidth) / 2 + this.x * imgWidth,
|
||||||
|
y: (frameArgs[1].height - imgHeight) / 2 + this.y * imgHeight
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
throw new Error('Coordinate: invalid reference frame for frameType: Canvas')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//Get Coordinates in Screen Reference Frame
|
||||||
|
if (frameArgs[2]) {
|
||||||
|
if (frameArgs[2].zoom && frameArgs[2].offset) {
|
||||||
|
outFrames.push({
|
||||||
|
x: outFrames[1].x * frameArgs[2].zoom + frameArgs[2].offset.x,
|
||||||
|
y: outFrames[1].y * frameArgs[2].zoom + frameArgs[2].offset.y
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
throw new Error('Coordinate: invalid reference frame for frameType: Screen')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return outFrames
|
||||||
|
}
|
||||||
|
|
||||||
|
toString() {
|
||||||
|
return `(x: ${this.x}, y: ${this.y})`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class StructureBox {
|
||||||
|
constructor(top, left, bottom, right) {
|
||||||
|
this.topLeft = new Coordinate(left, top)
|
||||||
|
this.bottomRight = new Coordinate(right, bottom)
|
||||||
|
}
|
||||||
|
|
||||||
|
getBoxes(boxType, ...frameArgs) {
|
||||||
|
let lowerH, lowerV, calcSide
|
||||||
|
switch (boxType) {
|
||||||
|
case 'point':
|
||||||
|
lowerH = 'right'
|
||||||
|
lowerV = 'bottom'
|
||||||
|
break
|
||||||
|
case 'side':
|
||||||
|
lowerH = 'width'
|
||||||
|
lowerV = 'height'
|
||||||
|
calcSide = true
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
throw new Error(`StructureBox: invalid boxType - ${boxType}`)
|
||||||
|
}
|
||||||
|
if (frameArgs.length == 0) {
|
||||||
|
return {
|
||||||
|
left: this.topLeft.x,
|
||||||
|
top: this.topLeft.y,
|
||||||
|
[lowerH]: this.bottomRight.x - ((calcSide) ? this.topLeft.x : 0),
|
||||||
|
[lowerV]: this.bottomRight.y - ((calcSide) ? this.topLeft.y : 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const tL = this.topLeft.toRefFrame(...frameArgs)
|
||||||
|
const bR = this.bottomRight.toRefFrame(...frameArgs)
|
||||||
|
let outBoxes = []
|
||||||
|
tL.forEach((cd, i) => {
|
||||||
|
outBoxes.push({
|
||||||
|
left: cd.x,
|
||||||
|
top: cd.y,
|
||||||
|
[lowerH]: bR[i].x - ((calcSide) ? cd.x : 0),
|
||||||
|
[lowerV]: bR[i].y - ((calcSide) ? cd.y : 0)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
return outBoxes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class Structure {
|
||||||
|
constructor(structResult) {
|
||||||
|
this.label = structResult.label
|
||||||
|
this.confidence = structResult.confidence
|
||||||
|
this.box = new StructureBox(
|
||||||
|
structResult.top,
|
||||||
|
structResult.left,
|
||||||
|
structResult.bottom,
|
||||||
|
structResult.right
|
||||||
|
)
|
||||||
|
this.deleted = false
|
||||||
|
this.index = -1
|
||||||
|
this.passThreshold = true
|
||||||
|
this.searched = false
|
||||||
|
}
|
||||||
|
|
||||||
|
get resultIndex() {
|
||||||
|
return this.index
|
||||||
|
}
|
||||||
|
|
||||||
|
set resultIndex(newIdx) {
|
||||||
|
this.index = newIdx
|
||||||
|
}
|
||||||
|
|
||||||
|
get isDeleted() {
|
||||||
|
return this.deleted
|
||||||
|
}
|
||||||
|
|
||||||
|
set isDeleted(del) {
|
||||||
|
this.deleted = !!del
|
||||||
|
}
|
||||||
|
|
||||||
|
get isSearched() {
|
||||||
|
return this.searched
|
||||||
|
}
|
||||||
|
|
||||||
|
set isSearched(ser) {
|
||||||
|
this.searched = !!ser
|
||||||
|
}
|
||||||
|
|
||||||
|
get aboveThreshold() {
|
||||||
|
return this.passThreshold
|
||||||
|
}
|
||||||
|
|
||||||
|
setThreshold(level) {
|
||||||
|
if (typeof level != 'number') {
|
||||||
|
throw new Error(`Structure: invalid threshold level ${level}`)
|
||||||
|
}
|
||||||
|
this.passThreshold = this.confidence >= level
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -144,6 +144,8 @@
|
|||||||
import touchMixin from './touch-mixin'
|
import touchMixin from './touch-mixin'
|
||||||
|
|
||||||
import detectionWorker from '@/assets/detect-worker.js?worker&inline'
|
import detectionWorker from '@/assets/detect-worker.js?worker&inline'
|
||||||
|
import Structure from '../js/structures'
|
||||||
|
import { StructureBox } from '../js/structures'
|
||||||
|
|
||||||
const regions = ['Thorax','Abdomen/Pelvis','Limbs','Head and Neck']
|
const regions = ['Thorax','Abdomen/Pelvis','Limbs','Head and Neck']
|
||||||
let activeRegion = 4
|
let activeRegion = 4
|
||||||
@@ -158,6 +160,7 @@
|
|||||||
let detectWorker = null
|
let detectWorker = null
|
||||||
let vidWorker = null
|
let vidWorker = null
|
||||||
let canvasMoving = false
|
let canvasMoving = false
|
||||||
|
let imageLocation = new StructureBox(0, 0, 1, 1)
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
mixins: [submitMixin, detectionMixin, cameraMixin, touchMixin],
|
mixins: [submitMixin, detectionMixin, cameraMixin, touchMixin],
|
||||||
@@ -286,14 +289,14 @@
|
|||||||
var filteredResults = this.resultData.detections
|
var filteredResults = this.resultData.detections
|
||||||
if (!filteredResults) return []
|
if (!filteredResults) return []
|
||||||
|
|
||||||
var allSelect = this.detectorLabels.every( s => { return s.detect } )
|
const allSelect = this.detectorLabels.every( s => { return s.detect } )
|
||||||
var selectedLabels = this.detectorLabels
|
const selectedLabels = this.detectorLabels
|
||||||
.filter( l => { return l.detect })
|
.filter( l => { return l.detect })
|
||||||
.map( l => { return l.name })
|
.map( l => { return l.name })
|
||||||
filteredResults.forEach( (d, i) => {
|
filteredResults.forEach( (d, i) => {
|
||||||
filteredResults[i].resultIndex = i
|
d.resultIndex = i
|
||||||
filteredResults[i].aboveThreshold = d.confidence >= this.detectorLevel
|
d.setThreshold(this.detectorLevel)
|
||||||
filteredResults[i].isSearched = allSelect || selectedLabels.includes(d.label)
|
d.isSearched = allSelect || selectedLabels.includes(d.label)
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!filteredResults.some( s => s.resultIndex == this.selectedChip && s.aboveThreshold && s.isSearched && !s.isDeleted)) {
|
if (!filteredResults.some( s => s.resultIndex == this.selectedChip && s.aboveThreshold && s.isSearched && !s.isDeleted)) {
|
||||||
@@ -333,15 +336,17 @@
|
|||||||
self = this
|
self = this
|
||||||
if (eDetect.data.error) {
|
if (eDetect.data.error) {
|
||||||
self.detecting = false
|
self.detecting = false
|
||||||
self.this.resultData = {}
|
self.resultData = {}
|
||||||
loadFailure()
|
loadFailure()
|
||||||
f7.dialog.alert(`ALVINN structure finding error: ${eDetect.data.message}`)
|
f7.dialog.alert(`ALVINN structure finding error: ${eDetect.data.message}`)
|
||||||
} else if (eDetect.data.success == 'detection') {
|
} else if (eDetect.data.success == 'detection') {
|
||||||
self.detecting = false
|
self.detecting = false
|
||||||
self.resultData = eDetect.data.detections
|
self.resultData = {detections: []}
|
||||||
if (self.resultData) {
|
eDetect.data.detections.detections.forEach((d) => {
|
||||||
self.resultData.detections.map(d => {d.label = self.detectorLabels[d.label].name})
|
d.label = self.detectorLabels[d.label].name
|
||||||
}
|
let detectedStructure = new Structure(d)
|
||||||
|
self.resultData.detections.push(detectedStructure)
|
||||||
|
})
|
||||||
self.uploadDirty = true
|
self.uploadDirty = true
|
||||||
} else if (eDetect.data.success == 'model') {
|
} else if (eDetect.data.success == 'model') {
|
||||||
reloadModel = false
|
reloadModel = false
|
||||||
@@ -476,33 +481,25 @@
|
|||||||
iChip = this.selectedChip
|
iChip = this.selectedChip
|
||||||
}
|
}
|
||||||
const [imCanvas, imageCtx] = this.resetView(true)
|
const [imCanvas, imageCtx] = this.resetView(true)
|
||||||
let structLeft = this.imageView.width * this.resultData.detections[iChip].left
|
let structBox, cvsBox, screenBox
|
||||||
let structTop = this.imageView.height * this.resultData.detections[iChip].top
|
[structBox, cvsBox, screenBox] = this.resultData.detections[iChip].box.getBoxes('side', this.imageView, imCanvas, {zoom: this.canvasZoom, offset: {...this.canvasOffset}})
|
||||||
let structWidth = this.imageView.width * (this.resultData.detections[iChip].right - this.resultData.detections[iChip].left)
|
|
||||||
let structHeight = this.imageView.height * (this.resultData.detections[iChip].bottom - this.resultData.detections[iChip].top)
|
|
||||||
|
|
||||||
const boxCoords = this.box2cvs(this.resultData.detections[iChip])[0]
|
this.infoLinkPos.x = Math.min(Math.max(screenBox.left, 0),imCanvas.width)
|
||||||
|
this.infoLinkPos.y = Math.min(Math.max(screenBox.top, 0), imCanvas.height)
|
||||||
|
|
||||||
let boxLeft = boxCoords.cvsLeft
|
const imageScale = Math.max(this.imageView.width / imCanvas.width, this.imageView.height / imCanvas.height)
|
||||||
let boxTop = boxCoords.cvsTop
|
imageCtx.drawImage(this.imageView, structBox.left, structBox.top, structBox.width, structBox.height, cvsBox.left, cvsBox.top, cvsBox.width, cvsBox.height)
|
||||||
let boxWidth = boxCoords.cvsRight - boxCoords.cvsLeft
|
|
||||||
let boxHeight = boxCoords.cvsBottom - boxCoords.cvsTop
|
|
||||||
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)
|
|
||||||
|
|
||||||
let imageScale = Math.max(this.imageView.width / imCanvas.width, this.imageView.height / imCanvas.height)
|
|
||||||
imageCtx.drawImage(this.imageView, structLeft, structTop, structWidth, structHeight, boxLeft, boxTop, boxWidth, boxHeight)
|
|
||||||
imageCtx.save()
|
imageCtx.save()
|
||||||
imageCtx.arc(boxLeft, boxTop, 14 / this.canvasZoom, 0, 2 * Math.PI)
|
imageCtx.arc(cvsBox.left, cvsBox.top, 14 / this.canvasZoom, 0, 2 * Math.PI)
|
||||||
imageCtx.closePath()
|
imageCtx.closePath()
|
||||||
imageCtx.clip()
|
imageCtx.clip()
|
||||||
imageCtx.drawImage(this.imageView,
|
imageCtx.drawImage(this.imageView,
|
||||||
structLeft - (14 / this.canvasZoom * imageScale),
|
structBox.left - (14 / this.canvasZoom * imageScale),
|
||||||
structTop - (14 / this.canvasZoom * imageScale),
|
structBox.top - (14 / this.canvasZoom * imageScale),
|
||||||
(28 / this.canvasZoom * imageScale),
|
(28 / this.canvasZoom * imageScale),
|
||||||
(28 / this.canvasZoom * imageScale),
|
(28 / this.canvasZoom * imageScale),
|
||||||
boxLeft - (14 / this.canvasZoom),
|
cvsBox.left - (14 / this.canvasZoom),
|
||||||
boxTop - (14 / this.canvasZoom),
|
cvsBox.top - (14 / this.canvasZoom),
|
||||||
(28 / this.canvasZoom), (28 / this.canvasZoom))
|
(28 / this.canvasZoom), (28 / this.canvasZoom))
|
||||||
imageCtx.restore()
|
imageCtx.restore()
|
||||||
this.selectedChip = iChip
|
this.selectedChip = iChip
|
||||||
@@ -532,13 +529,9 @@
|
|||||||
imageCtx.strokeStyle = 'yellow'
|
imageCtx.strokeStyle = 'yellow'
|
||||||
imageCtx.lineWidth = 3 / this.canvasZoom
|
imageCtx.lineWidth = 3 / this.canvasZoom
|
||||||
if (this.imageLoaded) {
|
if (this.imageLoaded) {
|
||||||
let imageLoc = this.box2cvs({top: 0,left: 0,right: 1,bottom: 1})
|
const imageLoc = imageLocation.getBoxes('side', this.imageView, imCanvas)
|
||||||
imCvsLocation.top = imageLoc[0].cvsTop
|
|
||||||
imCvsLocation.left = imageLoc[0].cvsLeft
|
|
||||||
imCvsLocation.width = imageLoc[0].cvsRight - imageLoc[0].cvsLeft
|
|
||||||
imCvsLocation.height = imageLoc[0].cvsBottom - imageLoc[0].cvsTop
|
|
||||||
if (drawChip) {imageCtx.globalAlpha = .5}
|
if (drawChip) {imageCtx.globalAlpha = .5}
|
||||||
imageCtx.drawImage(this.imageView, 0, 0, this.imageView.width, this.imageView.height, imCvsLocation.left, imCvsLocation.top, imCvsLocation.width, imCvsLocation.height)
|
imageCtx.drawImage(this.imageView, 0, 0, this.imageView.width, this.imageView.height, imageLoc[1].left, imageLoc[1].top, imageLoc[1].width, imageLoc[1].height)
|
||||||
if (drawChip) {imageCtx.globalAlpha = 1}
|
if (drawChip) {imageCtx.globalAlpha = 1}
|
||||||
}
|
}
|
||||||
this.structureZoomed = false
|
this.structureZoomed = false
|
||||||
@@ -592,12 +585,8 @@
|
|||||||
imCanvas.width = imCanvas.clientWidth
|
imCanvas.width = imCanvas.clientWidth
|
||||||
imCanvas.height = imCanvas.clientHeight
|
imCanvas.height = imCanvas.clientHeight
|
||||||
const imageCtx = imCanvas.getContext("2d")
|
const imageCtx = imCanvas.getContext("2d")
|
||||||
let imageLoc = this.box2cvs({top: 0,left: 0,right: 1,bottom: 1})
|
const imageLoc = imageLocation.getBoxes('side', this.imageView, imCanvas)
|
||||||
imCvsLocation.top = imageLoc[0].cvsTop
|
imageCtx.drawImage(this.imageView, 0, 0, this.imageView.width, this.imageView.height, imageLoc[1].left, imageLoc[1].top, imageLoc[1].width, imageLoc[1].height)
|
||||||
imCvsLocation.left = imageLoc[0].cvsLeft
|
|
||||||
imCvsLocation.width = imageLoc[0].cvsRight - imageLoc[0].cvsLeft
|
|
||||||
imCvsLocation.height = imageLoc[0].cvsBottom - imageLoc[0].cvsTop
|
|
||||||
imageCtx.drawImage(this.imageView, 0, 0, this.imageView.width, this.imageView.height, imCvsLocation.left, imCvsLocation.top, imCvsLocation.width, imCvsLocation.height)
|
|
||||||
f7.utils.nextFrame(() => {
|
f7.utils.nextFrame(() => {
|
||||||
this.setData()
|
this.setData()
|
||||||
})
|
})
|
||||||
@@ -624,7 +613,12 @@
|
|||||||
if (li >= numBoxes) li -= numBoxes
|
if (li >= numBoxes) li -= numBoxes
|
||||||
return li
|
return li
|
||||||
}
|
}
|
||||||
let boxCoords = this.box2cvs(this.showResults)
|
let boxCoords = []
|
||||||
|
this.resultData.detections.forEach(d => {
|
||||||
|
if (d.aboveThreshold && d.isSearched && !d.isDeleted) {
|
||||||
|
boxCoords.push(d.box.getBoxes('point',this.imageView,this.$refs.image_cvs)[1])
|
||||||
|
}
|
||||||
|
})
|
||||||
const numBoxes = boxCoords.length
|
const numBoxes = boxCoords.length
|
||||||
let clickX = (e.offsetX - this.canvasOffset.x) / this.canvasZoom
|
let clickX = (e.offsetX - this.canvasOffset.x) / this.canvasZoom
|
||||||
let clickY = (e.offsetY - this.canvasOffset.y) / this.canvasZoom
|
let clickY = (e.offsetY - this.canvasOffset.y) / this.canvasZoom
|
||||||
@@ -633,41 +627,13 @@
|
|||||||
var findBox = boxCoords.findIndex( (r, i) => {
|
var findBox = boxCoords.findIndex( (r, i) => {
|
||||||
let di = loopIndex(i)
|
let di = loopIndex(i)
|
||||||
if (di == this.selectedChip ) return false
|
if (di == this.selectedChip ) return false
|
||||||
return r.cvsLeft <= clickX &&
|
return r.left <= clickX &&
|
||||||
r.cvsRight >= clickX &&
|
r.right >= clickX &&
|
||||||
r.cvsTop <= clickY &&
|
r.top <= clickY &&
|
||||||
r.cvsBottom >= clickY &&
|
r.bottom >= clickY
|
||||||
this.resultData.detections[di].aboveThreshold &&
|
|
||||||
this.resultData.detections[di].isSearched &&
|
|
||||||
!this.resultData.detections[di].isDeleted
|
|
||||||
})
|
})
|
||||||
this.selectChip(findBox >= 0 ? this.resultData.detections[loopIndex(findBox)].resultIndex : this.selectedChip)
|
this.selectChip(findBox >= 0 ? this.resultData.detections[loopIndex(findBox)].resultIndex : this.selectedChip)
|
||||||
},
|
},
|
||||||
box2cvs(boxInput) {
|
|
||||||
if (!boxInput || boxInput.length == 0) return []
|
|
||||||
const boxList = boxInput.length ? boxInput : [boxInput]
|
|
||||||
const imCanvas = this.$refs.image_cvs
|
|
||||||
var imgWidth
|
|
||||||
var imgHeight
|
|
||||||
const imgAspect = this.imageView.width / this.imageView.height
|
|
||||||
const rendAspect = imCanvas.width / imCanvas.height
|
|
||||||
if (imgAspect >= rendAspect) {
|
|
||||||
imgWidth = imCanvas.width
|
|
||||||
imgHeight = imCanvas.width / imgAspect
|
|
||||||
} else {
|
|
||||||
imgWidth = imCanvas.height * imgAspect
|
|
||||||
imgHeight = imCanvas.height
|
|
||||||
}
|
|
||||||
const cvsCoords = boxList.map( (d, i) => {
|
|
||||||
return {
|
|
||||||
"cvsLeft": (imCanvas.width - imgWidth) / 2 + d.left * imgWidth,
|
|
||||||
"cvsRight": (imCanvas.width - imgWidth) / 2 + d.right * imgWidth,
|
|
||||||
"cvsTop": (imCanvas.height - imgHeight) / 2 + d.top * imgHeight,
|
|
||||||
"cvsBottom": (imCanvas.height - imgHeight) / 2 + d.bottom * imgHeight
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return cvsCoords
|
|
||||||
},
|
|
||||||
toggleSettings() {
|
toggleSettings() {
|
||||||
this.showDetectSettings = !this.showDetectSettings
|
this.showDetectSettings = !this.showDetectSettings
|
||||||
f7.utils.nextFrame(() => {
|
f7.utils.nextFrame(() => {
|
||||||
@@ -707,11 +673,11 @@
|
|||||||
},
|
},
|
||||||
zoomToSelected() {
|
zoomToSelected() {
|
||||||
const imCanvas = this.$refs.image_cvs
|
const imCanvas = this.$refs.image_cvs
|
||||||
const boxCoords = this.box2cvs(this.resultData.detections[this.selectedChip])[0]
|
const boxCoords = this.resultData.detections[this.selectedChip].box.getBoxes('point', this.imageView, imCanvas)
|
||||||
const boxWidth = boxCoords.cvsRight - boxCoords.cvsLeft
|
const boxWidth = boxCoords[1].right - boxCoords[1].left
|
||||||
const boxHeight = boxCoords.cvsBottom - boxCoords.cvsTop
|
const boxHeight = boxCoords[1].bottom - boxCoords[1].top
|
||||||
const boxMidX = (boxCoords.cvsRight + boxCoords.cvsLeft ) / 2
|
const boxMidX = (boxCoords[1].right + boxCoords[1].left ) / 2
|
||||||
const boxMidY = (boxCoords.cvsBottom + boxCoords.cvsTop ) / 2
|
const boxMidY = (boxCoords[1].bottom + boxCoords[1].top ) / 2
|
||||||
const zoomFactor = Math.min(imCanvas.width / boxWidth * .9, imCanvas.height / boxHeight * .9, 8)
|
const zoomFactor = Math.min(imCanvas.width / boxWidth * .9, imCanvas.height / boxHeight * .9, 8)
|
||||||
this.canvasZoom = zoomFactor
|
this.canvasZoom = zoomFactor
|
||||||
this.canvasOffset.x = -(boxMidX * zoomFactor) + imCanvas.width / 2
|
this.canvasOffset.x = -(boxMidX * zoomFactor) + imCanvas.width / 2
|
||||||
|
|||||||
Reference in New Issue
Block a user