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

Ссылка скопирована
1 ответ

У меня есть простой редактор, который я делаю на 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() }

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() // добавляет рамку управления вокруг изображения (тянуть, поворачивать, перетаскивать) },

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. О чём я:

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

Тут видно картинку, повёрнутую на 90 градусов и рамка управления (она специально не перевёрнута). Если тянуть рамку на право, то она передвигаться на право, а картинка передвигаться вниз. Я так понимаю это из за того, что rotate переворачивает весь canvas

Видел вот такой ответ. Там делают через матрицу (.setTransform). Но я так понимаю, этот способ не подойдёт, так как там идёт отрисовка прямоугольника по точкам, что не применимо к .drawImage.

Дополнительно:

Прежде всего, давайте рассмотрим, как работает центрированный поворот в 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(); }

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.

  • Большое спасибо за объяснение и решение проблемы
  • Щас начал доделывать и от сюда вытекает баг старой версии слежения за мышкой. Так как координаты без rotate. Буду искать и думать над формулой. Если есть какой то другой способ или решение буду признателен.
    (как найду, напишу сюда)

Ответы:

Ответ 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         }

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

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

Нужно решить такую задачу?

Опишите проблему, и специалист поможет с настройкой, исправлением ошибки или доработкой сайта. Подберём понятный план работ без лишней переписки.

Заказать помощь
Лучший ответ
1
Артём Dev Ответ

Для того чтобы понять логику центрированного поворота в canvas.js, нужно разобраться в некоторых основных принципах работы с библиотекой.

Canvas.js предоставляет возможность создания интерактивных графиков и диаграмм на HTML5 Canvas. Один из ключевых моментов при работе с canvas.js - это правильное определение точки центра вращения объекта.

В canvas.js центр вращения объекта задается с помощью метода setTransform() с использованием параметров translate() и rotate(). Например, если вы хотите центрировать поворот объекта вокруг его центра, вам нужно сначала сместить центр объекта в его текущее положение, а затем применить поворот.

Пример кода на JavaScript с использованием canvas.js для центрированного поворота объекта:

var canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext("2d");
 
// Определяем центр объекта
var centerX = canvas.width / 2;
var centerY = canvas.height / 2;
 
// Смещаем центр объекта в его текущее положение
ctx.translate(centerX, centerY);
 
// Применяем поворот
ctx.rotate(Math.PI / 4); // Например, поворот на 45 градусов
 
// Рисуем объект
ctx.fillRect(-50, -50, 100, 100); // Пример рисования квадрата

var canvas = document.getElementById("myCanvas"); var ctx = canvas.getContext("2d"); // Определяем центр объекта var centerX = canvas.width / 2; var centerY = canvas.height / 2; // Смещаем центр объекта в его текущее положение ctx.translate(centerX, centerY); // Применяем поворот ctx.rotate(Math.PI / 4); // Например, поворот на 45 градусов // Рисуем объект ctx.fillRect(-50, -50, 100, 100); // Пример рисования квадрата

В данном примере мы определяем центр объекта, смещаем его в текущее положение, применяем поворот и рисуем объект (в данном случае квадрат).

Надеюсь, данное объяснение поможет вам понять логику центрированного поворота в canvas.js. Если у вас остались вопросы или нужна дополнительная помощь, не стесняйтесь задавать.

Другие ответы (0)

Пока нет других ответов. Будьте первым, кто поможет автору.

Ответить на вопрос

комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Вам также может быть интересно