Javascript 缩放、旋转和平移后,画布绘制点定位到单击区域

Javascript 缩放、旋转和平移后,画布绘制点定位到单击区域,javascript,reactjs,typescript,canvas,Javascript,Reactjs,Typescript,Canvas,我有一个有点复杂的问题(我做了很多研究,但没有找到我要找的东西),基本上我正在构建一个标签工具,在那里我可以得到一组图像,我希望能够单击对象的角,并创建一个用户单击的点 需要注意的几件事(我已经做了这些) 图像可以是任何方向,我需要旋转它们(从方向旋转) 图像开始时应按比例缩小以适应画布(将比例设置为“缩小”图像和画布大小) 用户可以“平移”(根据箭头键进行翻译) 用户可以放大和缩小图像(使用shift+上/下箭头缩放) 用户可以将图像重置回中心(空格键居中,shift+空格键重置缩放初始和重

我有一个有点复杂的问题(我做了很多研究,但没有找到我要找的东西),基本上我正在构建一个标签工具,在那里我可以得到一组图像,我希望能够单击对象的角,并创建一个用户单击的点

需要注意的几件事(我已经做了这些)
  • 图像可以是任何方向,我需要旋转它们(从方向旋转)
  • 图像开始时应按比例缩小以适应画布(将比例设置为“缩小”图像和画布大小)
  • 用户可以“平移”(根据箭头键进行翻译)
  • 用户可以放大和缩小图像(使用shift+上/下箭头缩放)
  • 用户可以将图像重置回中心(空格键居中,shift+空格键重置缩放初始和重新居中)
现在的问题是,我正在构建单击部分(我在光标位置绘制一个点)。我尝试了多种方法来将鼠标坐标放置在正确的位置(考虑缩放、平移和旋转),但我很难将我的头绕在鼠标坐标上。我希望能得到一些帮助或指点,告诉你如何基本上反转旋转、缩放和平移我已经申请将点放置在正确的位置


为了给出一些真实的背景,我制作了一个代码笔来显示发生了什么

const red='#ff0000';
类应用程序扩展了React.Component{
私有画布:HTMLCanvasElement
私有映像=新映像
私有ctx:CanvasRenderingContext2D |空
私人资料:任何
私人定向:编号=270
私有移动键:{[key:number]:number}={}
私人cw:号码
私人ch:号码
私有scaleFactor:number=1.00
私人startX:号码
私人明星:号码
专用窗格:编号
私人公司:号码
私有:布尔值
私有defaultScaleFactor:number=1.00
专用图像点:编号[][]=[]
loadImage=(url:string)=>{
this.image.onload=()=>{
const iw=this.orientation==0 | | this.orientation==180?this.image.width:this.image.height
const ih=this.orientation==0 | | this.orientation==180?this.image.height:this.image.width
const size=Math.min(this.canvas.width/iw,this.canvas.height/ih)
this.defaultScaleFactor=更小
this.scaleFactor=更小
}
this.image.src=https://i.stack.imgur.com/EYvnM.jpg'
}
组件将卸载(){
document.removeEventListener('keyup',this.handleKeyUp)
document.removeEventListener('keydown',this.handleKeyDown)
//window.removeEventListener('resize',this.resizeCanvas)
this.canvas.removeEventListener('click',this.handleCanvasClick)
}
componentDidMount(){
this.isshift=false
document.addEventListener('keyup',this.handleKeyUp)
document.addEventListener('keydown',this.handleKeyDown)
//window.addEventListener('resize',this.resizeCanvas)//本例不需要
requestAnimationFrame(this.animate)
const elem=document.getElementById('canvasContainer')
如果(!elem)返回
const rect=elem.getBoundingClientRect()
this.canvas.addEventListener('click',this.handleCanvasClick)
this.canvas.width=rect.width
this.canvas.height=rect.height
this.ctx=this.canvas.getContext('2d')
this.cw=this.canvas.width
this.ch=this.canvas.height
this.startX=-(this.cw/2)
this.startY=-(this.ch/2)
this.panX=this.startX
this.panY=this.startY
这是loadImage()
}
handleCanvasClick=(e)=>{
让rect=this.canvas.getBoundingClientRect()
设x=e.clientX-rect.left
设y=e.clientY-rect.top
this.imagePoints.push([x,y])
}
动画=()=>{
Object.keys(this.moveKeys).forEach(key=>{
this.handleMovement(键,this.moveKeys[键])
})
这个
requestAnimationFrame(this.animate)
}
手部移动=(键、数量)=>{
常数移动单位=20
开关(parseInt(键)){
案例32://空格键
this.panX=this.startX
this.panY=this.startY
如果(按下此按钮){
this.scaleFactor=this.defaultScaleFactor
}
打破
案例37://左
如果(this.orientation==90 | | this.orientation==270){
this.panY-=移动单位
}否则{
this.panX-=moveUnit
}
打破
案例38://以上
如果(按下此按钮){
this.scaleFactor*=1.1
}否则{
如果(this.orientation==90 | | this.orientation==270){
this.panX+=moveUnit
}否则{
this.panY+=移动单位
}
}
打破
案例39://对
如果(this.orientation==90 | | this.orientation==270){
this.panY+=移动单位
}否则{
this.panX+=moveUnit
}
打破
案例40://向下
如果(按下此按钮){
this.scaleFactor/=1.1
}否则{
如果(this.orientation==90 | | this.orientation==270){
this.panX-=moveUnit
}否则{
this.panY-=移动单位
}
}
打破
违约:
打破
}
}
handleKeyUp=(e)=>{
if(e.shiftKey | | e.keyCode==16){
this.isshift=false
}
删除此项。移动键[e.keyCode]
}
handleKeyDown=(e)=>{
e、 预防默认值()
if(e.shiftKey | | e.keyCode==16){
this.isshift=true
}
e、 this.moveKeys中的keyCode?this.moveKeys[e.keyCode]+=1:this.moveKeys[e.keyCode]=1
}
drawTranslated=()=>{
如果(!this.ctx)返回
const ctx=this.ctx
clearRect(0,0,this.cw,this.ch)
ctx.save()
ctx.translate(this.cw/2,this.ch/2)
ctx.r
const red = '#ff0000';

class App extends React.Component<{}, {}> {
  private canvas: HTMLCanvasElement
  private image = new Image
  private ctx: CanvasRenderingContext2D | null
  private data: any
  private orientation: number = 270

  private moveKeys: {[key: number]: number} = {}
  private cw: number
  private ch: number
  private scaleFactor: number = 1.00
  private startX: number
  private startY: number
  private panX: number
  private panY: number
  private isShiftPressed: boolean
  private defaultScaleFactor: number = 1.00

  private imagePoints: number[][] = []

  loadImage = (url: string) => {
    this.image.onload = () => {
      const iw = this.orientation === 0 || this.orientation === 180 ? this.image.width : this.image.height
      const ih = this.orientation === 0 || this.orientation === 180 ? this.image.height : this.image.width
      const smaller = Math.min(this.canvas.width / iw, this.canvas.height / ih)
      this.defaultScaleFactor = smaller
      this.scaleFactor = smaller  
    }
    this.image.src = 'https://i.stack.imgur.com/EYvnM.jpg'
  }
  componentWillUnmount() {
    document.removeEventListener('keyup', this.handleKeyUp)
    document.removeEventListener('keydown', this.handleKeyDown)
    // window.removeEventListener('resize', this.resizeCanvas)
    this.canvas.removeEventListener('click', this.handleCanvasClick)
  }
  componentDidMount() {
    this.isShiftPressed = false
    document.addEventListener('keyup', this.handleKeyUp)
    document.addEventListener('keydown', this.handleKeyDown)
    // window.addEventListener('resize', this.resizeCanvas) // dont need for this example
    requestAnimationFrame(this.animate)
    const elem = document.getElementById('canvasContainer')
    if (!elem) return
  
    const rect = elem.getBoundingClientRect()

    this.canvas.addEventListener('click', this.handleCanvasClick)
    this.canvas.width = rect.width
    this.canvas.height = rect.height
    this.ctx = this.canvas.getContext('2d')
    this.cw = this.canvas.width
    this.ch = this.canvas.height

    this.startX = -(this.cw / 2)
    this.startY = -(this.ch / 2)
    this.panX = this.startX
    this.panY = this.startY
    
    this.loadImage()

  }
  handleCanvasClick = (e) => {
    let rect = this.canvas.getBoundingClientRect()
    let x = e.clientX - rect.left
    let y = e.clientY - rect.top
    this.imagePoints.push([x, y])
  }

  animate = () => {
    Object.keys(this.moveKeys).forEach( key => {
      this.handleMovement(key, this.moveKeys[key])
    })
    this.drawTranslated()
    requestAnimationFrame(this.animate)
  }
  handleMovement = (key, quantity) => {
    const moveUnit = 20
    switch (parseInt(key)) {
      case 32: // spacebar
        this.panX = this.startX
        this.panY = this.startY
        if (this.isShiftPressed) {
          this.scaleFactor = this.defaultScaleFactor
        }
        break
      case 37: // left
        if (this.orientation === 90 || this.orientation === 270) {
          this.panY -= moveUnit
        } else {
          this.panX -= moveUnit
        }
        break
      case 38: // up
        if (this.isShiftPressed) {
          this.scaleFactor *= 1.1
        } else {
          if (this.orientation === 90 || this.orientation === 270) {
            this.panX += moveUnit
          } else {
            this.panY += moveUnit
          }
        }
        break
      case 39: // right
        if (this.orientation === 90 || this.orientation === 270) {
          this.panY += moveUnit
        } else {
          this.panX += moveUnit
        }
        break
      case 40: // down
        if (this.isShiftPressed) {
          this.scaleFactor /= 1.1
        } else {
          if (this.orientation === 90 || this.orientation === 270) {
            this.panX -= moveUnit
          } else {
            this.panY -= moveUnit
          }
        }
        break
      default:
        break
    }
  }

  handleKeyUp = (e) => {
    if (e.shiftKey || e.keyCode === 16) {
      this.isShiftPressed = false
    }
    delete this.moveKeys[e.keyCode]
  }
  handleKeyDown = (e) => {
    e.preventDefault()
    if (e.shiftKey || e.keyCode === 16) {
      this.isShiftPressed = true
    }
    e.keyCode in this.moveKeys ? this.moveKeys[e.keyCode] += 1 : this.moveKeys[e.keyCode] = 1
  }

  drawTranslated = () => {
    if (!this.ctx) return
    const ctx = this.ctx
    ctx.clearRect(0, 0, this.cw, this.ch)
    ctx.save()
    ctx.translate(this.cw / 2, this.ch / 2)
    ctx.rotate(this.orientation * Math.PI / 180)
    ctx.scale(this.scaleFactor, this.scaleFactor)
    ctx.translate(this.panX, this.panY)

    const transformedWidth = this.canvas.width / 2 - this.image.width / 2
    const transformedHeight = this.canvas.height / 2 - this.image.height / 2
    ctx.drawImage(
      this.image,
      transformedWidth,
      transformedHeight
    )
    
    const pointSize = 10
    if (this.imagePoints.length > 0) {
      this.imagePoints.forEach( ([x, y]) => {
        ctx.fillStyle = red
        ctx.beginPath()
        // Obviously the x and y here need to be transformed to work with the current scale, rotation and translation. But I'm stuck here!
        ctx.arc(x, y, pointSize, 0, Math.PI * 2, true)
        ctx.closePath()
        ctx.fill()
      })
    }
    ctx.restore()
  }
  handleResetUserClicks = () => {
    this.imagePoints = []
  }
  render() {
    return (
      <div id="container">
        <div>Use arrow keys to pan the canvas, shift + up / down to zoom, spacebar to reset</div>
        <div id="canvasContainer">
          <canvas ref={this.assignCameraRef} id="canvasElement" style={{ position: 'absolute' }} ref={this.assignCameraRef} />
        </div>
        <div>
          <button onClick={this.handleResetUserClicks}>Reset Clicks</button>
        </div>
      </div>
    )
  }
  assignCameraRef = (canvas: HTMLCanvasElement) => this.canvas = canvas
}
// NOTE rotate is in radians
// with panX, and panY added
var matrix = [1,0,0,1,0,0];
var invMatrix = [1,0,0,1];
function createMatrix(x, y, scale, rotate, panX, panY){
    var m = matrix; // just to make it easier to type and read
    var im = invMatrix; // just to make it easier to type and read

    // create the rotation and scale parts of the matrix
    m[3] =   m[0] = Math.cos(rotate) * scale;
    m[2] = -(m[1] = Math.sin(rotate) * scale);

    // add the translation
    m[4] = x;
    m[5] = y;

    // transform pan and add to the position part of the matrix
    m[4] += panX * m[0] + panY * m[2];
    m[5] += panX * m[1] + panY * m[3];    

    //=====================================
    // calculate the inverse transformation

    // first get the cross product of x axis and y axis
    cross = m[0] * m[3] - m[1] * m[2];

    // now get the inverted axis
    im[0] =  m[3] / cross;
    im[1] = -m[1] / cross;
    im[2] = -m[2] / cross;
    im[3] =  m[0] / cross;
 }