Холст рисовать точки, локализованные в области нажатия после масштабирования, поворота и перевода - PullRequest
0 голосов
/ 31 мая 2018

У меня есть немного сложный вопрос (я провел много исследований, но не смог найти то, что искал), в основном я создаю инструмент маркировки, где я получаю набор изображений, и яхотите быть в состоянии щелкнуть углы объектов и создать точку, где пользователь нажимает.

Несколько замечаний (я уже сделал это)

  • Изображения могут быть любой ориентации, и мне нужно повернуть их (повернуть из ориентации)
  • Изображение должно начинаться с масштабирования по размеру холста (настройка масштаба для «уменьшения» по размеру изображения и холста)
  • Пользователи могут «перемещаться» (перевод основан на клавиши со стрелками )
  • Пользователи могут увеличивать и уменьшать изображение (масштаб с помощью shift + стрелка вверх / вниз )
  • Пользователи могут сбросить изображение обратно в центр ( пробел) центров, Shift + пробел сбрасывает начальный масштаб и повторно центрируется)

У меня сейчас проблема в том, что я строю часть щелчка (где я рисую точкув месте расположения курсора).Я пробовал несколько вещей, чтобы поместить координаты мыши в правильном месте (с учетом масштаба, перемещения и поворота), и мне трудно оборачиваться вокруг него.Хотелось бы получить помощь или указатели о том, как в принципе инвертировать вращение, масштаб и перевод Я подал заявку, чтобы получить точку в правильном месте.


Чтобы дать немного реальногоВ контексте этого я создал Codepen, чтобы показать, что происходит.

Codepen, чтобы увидеть его вживую с помощью клавиш со стрелками / щелчков на холсте

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
}

Пожалуйста, игнорируйтеотсутствие определенного реквизита и несколько жестко закодированных значений (например, ориентации).Я удалил немного кода и абстрагировал его, чтобы сделать его более общим, и часть этого означала жесткое кодирование URL-адреса изображения с фиктивным, который я нашел в Интернете, и настройку некоторых параметров для этого изображения.

1 Ответ

0 голосов
/ 31 мая 2018

Инвертирование преобразования

Обратное преобразование для поиска мировых координат.

В этом случае мировые координаты представляют собой пиксельные координаты изображения, и функция toWorld преобразуетот координат холста до мировых координат.

Однако вы переводите в cx, cy, вращаете, масштабируете, а затем переводите панорамированием.Вам нужно будет умножить координаты панорамирования на матрицу 3 вышеупомянутых преобразований и добавить ее к двум последним значениям матрицы, прежде чем вычислять обратное преобразование.

Примечание вы панорамировалидважды * один раз для this.panX, this.panY, а затем выполняется панорамирование с помощью transformedWidth и transformedHeight. Для функции ниже требуется полный панорамирование this.panX + transformedWidth и this.panY + transformedHeight в качестве двух последних аргументов.

Измененныйфункция из связанного ответа is

// 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;
 }  

Затем можно использовать функцию toWorld из связанного ответа , чтобы получить мировые координаты (координаты в пространстве изображения)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...