DonTraffic
@DonTraffic
Frontend-developer

Не могу понять логику центрированного поворота в canvas.js, кто то может объяснить?

У меня есть простой редактор, который я делаю на nuxt3. В нём можно: загружать картинки, перетаскивать, растягивать, наслаивать. Но я не могу допереть, как сделать правильный центрированный rotate для одной картинки.

что я сделал:
у меня слежка на мышке, когда перетаскивается элемент, ему просто присваиваются значение позиции мышки (+ сделал offset)
if (
    item.x <= e.offsetX & item.x+item.width >= e.offsetX &
    item.y <= e.offsetY & item.y+item.height >= e.offsetY
){
    if (!this.canvas.offsetX) this.canvas.offsetX = e.offsetX - item.x
    if (!this.canvas.offsetY) this.canvas.offsetY = e.offsetY - item.y

    item.x = e.offsetX - this.canvas.offsetX
    item.y = e.offsetY - this.canvas.offsetY

    this.updateImageCanvas()
}


updateImageCanvas() {
     this.canvas.ctx.clearRect(0, 0, this.canvas.item.width, this.canvas.item.height);
     let setting = this.catalogItems[this.activeCatalogItem].setting

      this.canvas.ctx.translate(setting.product.width/2, -setting.product.height/2); // вот тут сложности, не понимаю логику как сделать её по центру
      this.canvas.ctx.rotate((90 * Math.PI) / 180) // поворачиваю на 90 градусов
      this.canvas.ctx.drawImage(
            setting.product.canvasImage, 
            setting.product.x, 
            setting.product.y, 
            setting.product.width, 
            setting.product.height
       )
       this.canvas.ctx.rotate((0 * Math.PI) / 180) // возвращаю в норму градусы
       this.canvas.ctx.setTransform(1, 0, 0, 1, 0, 0); // это сбрасывает матрицу, не совсем понимаю зачем, но без этого нет стабильности

       this.printImageSetting() // добавляет рамку управления вокруг изображения (тянуть, поворачивать, перетаскивать)
},


Трудности:
--- даже если я разберусь с translate, и он будет центрирован, то мне это не поможет, так как при таком способе (скорее всего я что то упустил или не понял) глобально меняется направление canvas. О чём я:
65bbcbf8e92ff624961347.png
Тут видно картинку, повёрнутую на 90 градусов и рамка управления (она специально не перевёрнута). Если тянуть рамку на право, то она передвигаться на право, а картинка передвигаться вниз. Я так понимаю это из за того, что rotate переворачивает весь canvas

Видел вот такой ответ. Там делают через матрицу (.setTransform). Но я так понимаю, этот способ не подойдёт, так как там идёт отрисовка прямоугольника по точкам, что не применимо к .drawImage.
  • Вопрос задан
  • 85 просмотров
Решения вопроса 1
@MariaGurkina
Прежде всего, давайте рассмотрим, как работает центрированный поворот в Canvas. Когда вы вызываете translate для перемещения центра координат, затем поворачиваете canvas и рисуете изображение, оно будет поворачиваться относительно установленного центра.

Чтобы правильно центрировать поворот изображения, вам нужно определить новые координаты для его отрисовки. Вместо того чтобы использовать translate, вы можете рассчитать новые координаты, которые учитывают центр изображения.

Вот как можно модифицировать ваш код:
updateImageCanvas() {
    this.canvas.ctx.clearRect(0, 0, this.canvas.item.width, this.canvas.item.height);
    let setting = this.catalogItems[this.activeCatalogItem].setting;

    // Сохраняем текущее состояние контекста
    this.canvas.ctx.save();

    // Рассчитываем новые координаты для центрированного поворота
    let centerX = setting.product.x + setting.product.width / 2;
    let centerY = setting.product.y + setting.product.height / 2;

    // Применяем трансформации
    this.canvas.ctx.translate(centerX, centerY);
    this.canvas.ctx.rotate((90 * Math.PI) / 180);
    
    // Отрисовываем изображение
    this.canvas.ctx.drawImage(
        setting.product.canvasImage, 
        -setting.product.width / 2, 
        -setting.product.height / 2, 
        setting.product.width, 
        setting.product.height
    );

    // Восстанавливаем состояние контекста
    this.canvas.ctx.restore();

    this.printImageSetting();
}


В этом коде save и restore используются для сохранения и восстановления состояния контекста, чтобы изменения, внесенные translate и rotate, не влияли на другие части кода.

Рассчитываются новые координаты для центрированного поворота, и при отрисовке изображения используются эти координаты. Таким образом, изображение будет поворачиваться относительно своего центра, а не угла canvas.
Ответ написан
Пригласить эксперта
Ответы на вопрос 1
DonTraffic
@DonTraffic Автор вопроса
Frontend-developer
Ответ MariaGurkina (@MariaGurkina) очень сильно помог, я пишу этот ответ, что бы закрыть (предсказуемый) баг с отслеживанием клика, вытекающий из его ответа. Так как теперь нельзя следить за нажатием именно в правый верхний угол да и некоторые участки картинки не перетаскиваются, потому что для координат, картинка не повёрнута.

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

Формулу брал от сюда

в vpoints последняя ячейка используется для записи минимального и максимального значения координат, что бы при нажатии на любое место картинки, срабатывала слежка за курсором

// angle - угол в радианах
    let angle = item.rotate * (Math.PI/180)
    // center - точка, вокруг которой происходит вращение
    let center = {
        x: item.x + item.width/2,
        y: item.y + item.height/2,
    }
    // points - массив с координатами вершин прямоугольника
    let points = [
        {
            x: item.x,
            y: item.y
        },
        {
            x: item.x + item.width,
            y: item.y
        },
        {
            x: item.x + item.width,
            y: item.y + item.height
        },
        {
            x: item.x,
            y: item.y + item.height
        },
    ]
    // vpoints - массив с повернутыми координатами вершин
    let vpoints = [{}, {}, {}, {}, {
        max: {
            x:0,
            y:0
        },
        min: {
            x: item.x,
            y: item.y
        }
    }]
        
    let ssin = Math.sin(angle);
    let scos = Math.cos(angle);
        
    for (let i = 0; i < points.length; i++) {
        vpoints[i].x = center.x + (points[i].x - center.x) * scos - (points[i].y - center.y) * ssin;
            if(vpoints[i].x > vpoints[vpoints.length-1].max.x) vpoints[vpoints.length-1].max.x = vpoints[i].x
            if(vpoints[i].x < vpoints[vpoints.length-1].min.x) vpoints[vpoints.length-1].min.x = vpoints[i].x
            vpoints[i].y = center.y + (points[i].x - center.x) * ssin + (points[i].y - center.y) * scos;
            if(vpoints[i].y > vpoints[vpoints.length-1].max.y) vpoints[vpoints.length-1].max.y = vpoints[i].y
            if(vpoints[i].y < vpoints[vpoints.length-1].min.y) vpoints[vpoints.length-1].min.y = vpoints[i].y
        }

        // rotate (упрощённая)
        if (
            this.canvas.rotate ||
            vpoints[1].x-6 <= e.offsetX && vpoints[1].x+6 >= e.offsetX &&
            vpoints[1].y-6 <= e.offsetY && vpoints[1].y+6 >= e.offsetY 
        ){
            console.log('rotate');

            this.canvas.rotate = true
            item.rotate = e.offsetY

            this.updateImageCanvas()
            return
        }

        // move
        if (
            vpoints[vpoints.length-1].min.x <= e.offsetX && vpoints[vpoints.length-1].max.x >= e.offsetX &&
            vpoints[vpoints.length-1].min.y <= e.offsetY && vpoints[vpoints.length-1].max.y >= e.offsetY
        ){
            if (!this.canvas.offsetX) this.canvas.offsetX = e.offsetX - item.x
            if (!this.canvas.offsetY) this.canvas.offsetY = e.offsetY - item.y

            item.x = e.offsetX - this.canvas.offsetX
            item.y = e.offsetY - this.canvas.offsetY

            this.updateImageCanvas()
            return
        }


Извените, нужно будет убраться в коде, но для понимания сути подойдёт
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы