Russian Qt Forum
Ноябрь 02, 2024, 10:16 *
Добро пожаловать, Гость. Пожалуйста, войдите или зарегистрируйтесь.
Вам не пришло письмо с кодом активации?

Войти
 
  Начало   Форум  WIKI (Вики)FAQ Помощь Поиск Войти Регистрация  

Страниц: [1]   Вниз
  Печать  
Автор Тема: Масштабирование с сохранением позиции курсора над объектом  (Прочитано 12245 раз)
Nimbus
Гость
« : Октябрь 17, 2010, 11:38 »

Здравствуйте. Дело в том, что я раньше занимался программированием сетевых многопоточных приложений/баз данных, а тут вдруг приспичило прокачать скиллы работы с двумерной графикой. Вобщем, делаю небольшой графический редактор. И возникла необходимость масштабировать графические элементы, причём, чтобы сохранялась позиция курсора мыши над элементом, т. е. чтобы во время скейлинга объекты не "уезжали" из под курсора куда-либо.
Использую для задания позиции объектов и их масштаба объект QMatrix, который потом передаю в QPainter

Код такой:
Код
C++ (Qt)
void DrawWidget::paintEvent(QPaintEvent *pe) { // Перерисовка
QPainter painter(this);
QMatrix matrix;
matrix.scale(scale, scale); //Масштабирование
matrix.translate(xOffset, yOffset); //Транслирование системы координат
painter.setMatrix(matrix);
painter.setRenderHint( QPainter::Antialiasing );
for (int i = 0; i < objectsCount; i++)
objects[i]->Draw(&painter);
}
 
void DrawWidget::mousePressEvent(QMouseEvent *pe) { //Нажатие кнопки мыши
if (pe->button() == Qt::LeftButton) {
down = true;
dragStartPosition = pe->pos();
setCursor(QCursor(Qt::ClosedHandCursor));
UnHighlightAllObjects();
for (int i = 0; i < objectsCount; i++)
if (objects[i]->Belongs(pe->x() / scale - xOffset, pe->y() / scale - yOffset)) {
SelectObject(objects[i]);
return;
}
}
repaint();
}
 
void DrawWidget::mouseMoveEvent(QMouseEvent *pe) {//Перемещение мыши
if (!down || !(pe->buttons() & Qt::LeftButton))
return;
xOffset -= (dragStartPosition.x() - pe->x()) / scale;
yOffset -= (dragStartPosition.y() - pe->y()) / scale;
dragStartPosition = pe->pos();
repaint();
}
 
void DrawWidget::mouseReleaseEvent(QMouseEvent *pe) {//Отпустили кнопку мыши
down = false;
setCursor(QCursor(Qt::ArrowCursor));
}
 
void DrawWidget::wheelEvent(QWheelEvent *pe) { //Прокрутка колёсика мыши
float oldScale = scale;
 
if (invertMouse)
scale -= pe->delta() / 1000.f;
else
scale += pe->delta() / 1000.f;
if (scale > 5.f)
scale = 5.f;
if (scale < 0.1f)
scale = 0.1f;
repaint();
}
 
Собственно, всё, что тут делается, это изменяет систему координат QPainter'а. То есть три поля класса xOffset, yOffset и scale объявлены как qreal. Требуется дописать код в wheelEvent для изменения xOffset и yOffset в зависимости от позиции курсора над виджетом.
Исходя из формул, можно сказать, что
scale1*x + xoffset1 = scale2*x + xoffset2 => xoffset2 = scale1*x + xoffset1 - scale2*x

Следовательно код:
Код
C++ (Qt)
void DrawWidget::wheelEvent(QWheelEvent *pe) {
   float oldScale = scale;
   if (invertMouse)
       scale -= pe->delta() / 1000.f;
   else
       scale += pe->delta() / 1000.f;
   if (scale > 5.f)
       scale = 5.f;
   if (scale < 0.1f)
       scale = 0.1f;
   xOffset = ((oldScale * pe->x()) + xOffset) - (scale * pe->x());
   yOffset = ((oldScale * pe->y()) + yOffset) - (scale * pe->y());
   repaint();
}
 
должен по идее работать. Однако, когда запускаю на выполнение и пытаюсь скейлить, то создаётся впечатление, что мы приближаемся к объекту по спиралеобразной траектории и позиция над объектом курсора никак не сохраняется Грустный
Кто уже решал подобную проблему, поделитесь советом как исправить ситуацию, пожалуйста.
« Последнее редактирование: Октябрь 17, 2010, 11:51 от JC » Записан
marbius
Гость
« Ответ #1 : Октябрь 17, 2010, 18:05 »

Такой вариант Вам подойдет?


Код
C++ (Qt)
MainWindow::MainWindow(QWidget *parent) :
   QMainWindow(parent),
   ui(new Ui::MainWindow)
{
   ui->setupUi(this);
}
 
void MainWindow::paintEvent(QPaintEvent *e)
{
   QPainter painter(this);
   painter.setMatrix(m);
   painter.drawRect(100,100,100,100);
   painter.drawRect(200,200,100,100);
   painter.end();
}
 
void MainWindow::wheelEvent(QWheelEvent *e)
{   float scale = 1+e->delta() / 1000.f;
   float xOffset = (e->x() - scale * e->x())/scale;
   float yOffset = (e->y() - scale * e->y())/scale;
   m.scale(scale,scale);
   m.translate(xOffset,yOffset);
   repaint();
}
MainWindow::~MainWindow()
{
   delete ui;
}
 

Код
C++ (Qt)
class MainWindow : public QMainWindow
{
   Q_OBJECT
 
public:
   explicit MainWindow(QWidget *parent = 0);
   ~MainWindow();
 
private:
   Ui::MainWindow *ui;
   void paintEvent(QPaintEvent *e);
   void wheelEvent(QWheelEvent *e);
 
   QMatrix m;
};
 
« Последнее редактирование: Октябрь 17, 2010, 18:14 от µarbius » Записан
Nimbus
Гость
« Ответ #2 : Октябрь 18, 2010, 04:22 »

Такой вариант Вам подойдет?
Нет. Спиралеобразного эффекта нет, но и объекты "уезжают".
Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #3 : Октябрь 18, 2010, 09:28 »

Делал, но не на Qt. Смысл такой: находите относительные координаты курсора (от 0 до 1). Они не меняются  при масштабировании. Расчет для x

float relative_x = (cursor_x + x_offset) / (image_width * old_scale);

и получаете новый x_offset

x_offset = relative_x * (image_width * new_scale) - cursor_x;

Понятно image_width сокращается. И еще: здесь полагаем что сначала мы применили масштаб (scale) а потом смещение (translate)
Записан
Nimbus
Гость
« Ответ #4 : Октябрь 22, 2010, 20:09 »

Увы, так тоже неверно. При увеличении объекты едут в верхний левый угол виджета, а при уменьшении - в правый нижний.
Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #5 : Октябрь 22, 2010, 20:16 »

Увы, так тоже неверно. При увеличении объекты едут в верхний левый угол виджета, а при уменьшении - в правый нижний.
А в каком порядке Вы вызываете трансформации для QPainter?
Записан
Nimbus
Гость
« Ответ #6 : Октябрь 25, 2010, 04:31 »

А в каком порядке Вы вызываете трансформации для QPainter?
Код в первом посте смотрите. В событии paintEvent
Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #7 : Октябрь 25, 2010, 09:04 »

Код в первом посте смотрите. В событии paintEvent
Точно, виноват  Улыбающийся

Исходя из формул, можно сказать, что
scale1*x + xoffset1 = scale2*x + xoffset2 => xoffset2 = scale1*x + xoffset1 - scale2*x
Здесь "х" есть начальная позиция которая переводится в позицию курсора в координатах окна, то есть:

scale1*x + xoffset1 = x_cursor;

отсюда x = (x_cursor - xoffset1) / scale1;
тогда x_offset2 = x_cursor - x*scale2;
Записан
GreatSnake
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2921



Просмотр профиля
« Ответ #8 : Октябрь 25, 2010, 09:15 »

Может пусть сам Qt будет делать позиционирование
QGraphicsView::setTransformationAnchor( QGraphicsView::AnchorUnderMouse )?
Записан

Qt 5.11/4.8.7 (X11/Win)
Nimbus
Гость
« Ответ #9 : Ноябрь 11, 2010, 13:59 »

тогда x_offset2 = x_cursor - x*scale2;
Ага, и если ещё развернуть формулу при scale1*x + xoffset1 = x_cursor, то мы получим формулу
x_offset2 = scale1*x + xoffset1 - x*scale2
которую я уже писал в своём первом посте
xoffset2 = scale1*x + xoffset1 - scale2*x
и она не работает.

Может пусть сам Qt будет делать позиционирование
QGraphicsView::setTransformationAnchor( QGraphicsView::AnchorUnderMouse )?
Эммм... Я рисую на виджете Т_Т
Записан
GreatSnake
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2921



Просмотр профиля
« Ответ #10 : Ноябрь 11, 2010, 14:52 »

Цитировать
Эммм... Я рисую на виджете Т_Т
Да, действительно, чего-то сразу и не приметил.
Так может тогда перейти на QGraphicsView, коли уже столько времени решаете проблему?
Записан

Qt 5.11/4.8.7 (X11/Win)
p166
Гость
« Ответ #11 : Ноябрь 13, 2010, 14:15 »

Совсем недавно занимался такой же проблемой при масштабировании графиков, окончательный вариант был таким:
1. делаем масштабирование
2. центрируем изображение относительно точки масштабирования

profit!
Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #12 : Ноябрь 13, 2010, 16:53 »

и она не работает.
Я бы заменил object[j]->draw() на простой тест, напр рисование квадрата 10х10, левый верхний угол в точке (100, 100). По событию нажатия мыши свалил бы в консоль всю информацию: масштаб, смещения и позицию мыши. После минут 10 "анализа" формула/проблема была бы ясна. А так раз в месяц пробовать - долговато  Улыбающийся
Записан
Страниц: [1]   Вверх
  Печать  
 
Перейти в:  


Страница сгенерирована за 0.372 секунд. Запросов: 23.