Russian Qt Forum

Qt => 2D и 3D графика => Тема начата: Nimbus от Октябрь 17, 2010, 11:38



Название: Масштабирование с сохранением позиции курсора над объектом
Отправлено: 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 в зависимости от позиции курсора над виджетом.
Исходя из формул (http://doc.trolltech.com/4.7/qmatrix.html#basic-matrix-operations), можно сказать, что
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();
}
 
должен по идее работать. Однако, когда запускаю на выполнение и пытаюсь скейлить, то создаётся впечатление, что мы приближаемся к объекту по спиралеобразной траектории и позиция над объектом курсора никак не сохраняется :(
Кто уже решал подобную проблему, поделитесь советом как исправить ситуацию, пожалуйста.


Название: Re: Масштабирование с сохранением позиции курсора над объектом
Отправлено: marbius от Октябрь 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;
};
 


Название: Re: Масштабирование с сохранением позиции курсора над объектом
Отправлено: Nimbus от Октябрь 18, 2010, 04:22
Такой вариант Вам подойдет?
Нет. Спиралеобразного эффекта нет, но и объекты "уезжают".


Название: Re: Масштабирование с сохранением позиции курсора над объектом
Отправлено: Igors от Октябрь 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)


Название: Re: Масштабирование с сохранением позиции курсора над объектом
Отправлено: Nimbus от Октябрь 22, 2010, 20:09
Увы, так тоже неверно. При увеличении объекты едут в верхний левый угол виджета, а при уменьшении - в правый нижний.


Название: Re: Масштабирование с сохранением позиции курсора над объектом
Отправлено: Igors от Октябрь 22, 2010, 20:16
Увы, так тоже неверно. При увеличении объекты едут в верхний левый угол виджета, а при уменьшении - в правый нижний.
А в каком порядке Вы вызываете трансформации для QPainter?


Название: Re: Масштабирование с сохранением позиции курсора над объектом
Отправлено: Nimbus от Октябрь 25, 2010, 04:31
А в каком порядке Вы вызываете трансформации для QPainter?
Код в первом посте смотрите. В событии paintEvent


Название: Re: Масштабирование с сохранением позиции курсора над объектом
Отправлено: Igors от Октябрь 25, 2010, 09:04
Код в первом посте смотрите. В событии paintEvent
Точно, виноват  :)

Исходя из формул (http://doc.trolltech.com/4.7/qmatrix.html#basic-matrix-operations), можно сказать, что
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;


Название: Re: Масштабирование с сохранением позиции курсора над объектом
Отправлено: GreatSnake от Октябрь 25, 2010, 09:15
Может пусть сам Qt будет делать позиционирование
QGraphicsView::setTransformationAnchor( QGraphicsView::AnchorUnderMouse )?


Название: Re: Масштабирование с сохранением позиции курсора над объектом
Отправлено: Nimbus от Ноябрь 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 )?
Эммм... Я рисую на виджете Т_Т


Название: Re: Масштабирование с сохранением позиции курсора над объектом
Отправлено: GreatSnake от Ноябрь 11, 2010, 14:52
Цитировать
Эммм... Я рисую на виджете Т_Т
Да, действительно, чего-то сразу и не приметил.
Так может тогда перейти на QGraphicsView, коли уже столько времени решаете проблему?


Название: Re: Масштабирование с сохранением позиции курсора над объектом
Отправлено: p166 от Ноябрь 13, 2010, 14:15
Совсем недавно занимался такой же проблемой при масштабировании графиков, окончательный вариант был таким:
1. делаем масштабирование
2. центрируем изображение относительно точки масштабирования

profit!


Название: Re: Масштабирование с сохранением позиции курсора над объектом
Отправлено: Igors от Ноябрь 13, 2010, 16:53
и она не работает.
Я бы заменил object[j]->draw() на простой тест, напр рисование квадрата 10х10, левый верхний угол в точке (100, 100). По событию нажатия мыши свалил бы в консоль всю информацию: масштаб, смещения и позицию мыши. После минут 10 "анализа" формула/проблема была бы ясна. А так раз в месяц пробовать - долговато  :)