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

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

Страниц: [1] 2 3   Вниз
  Печать  
Автор Тема: QGraphicsItem и трансформации систем координат  (Прочитано 23226 раз)
iroln
Гость
« : Август 07, 2011, 13:49 »

Здравствуйте!

Сначала кратко опишу, что я хочу сделать, а затем опишу проблему, с которой я столкнулся.

Итак. Я разрабатываю некоторый инструментарий для отображения и работы с изображением. Графическая сцена (QGraphicsScene) всегда имеет размер оригинального изображения, а графическое представление (QGraphicsView) кроме сцены отображает изображение с заданными параметрами масштаба и сдвига. Изображение рисуется специальным рисовальщиком не на background-слой сцены, а в viewport уже с заданными параметрами отображения (масштаб, сдвиг и т.д.). Масштабом отображения сцены и изображения управляет графическое представление с помощью своей матрицы преобразования.

Кроме отображения изображения, мне нужны инструменты для работы с изображением. Первый инструмент - это интерактивная рамка (ImageRectItem). Пользователь может перемещать эту рамку по изображению, изменять её размеры, таская мышкой за края рамки и специальные маркеры:


При разработке такого инструмента я и столкнулся с проблемой.
Инструмент ImageRectItem реализован на базе класса QGraphicsRectItem, ручки-маркеры ImageRectMarkerItem реализованы тоже на базе QGraphicsRectItem и являются потомками ImageRectItem, они обеспечивают возможность изменения размера рамки (resize). Проблема вот в чём. Необходимо, чтобы размеры ручек-маркеров находились в системе координат области отображения и не зависели от текущего масштаба вида, то есть при масштабировании матрицы представления, размеры ручек маркеров не изменялись, чтобы не было вот такого:


Я попытался решить эту проблему, выставив для ImageRectMarkerItem флаг ItemIgnoresTransformations в true, но проблему это не решило. Размеры теперь не изменяются, но теперь ручки-маркеры стали "жить" в системе координат области отображения и зависеть от scale области отображения:

То есть локальные координаты рамок ручек-маркеров соответствуют системе координат сцены, поэтому неверно отображаются на виде.

Затем я попытался переопределить метод paint в классе ImageRectMarkerItem и рисовать ручки-маркеры как мне нужно, игнорируя scale, но появилась проблема с boundingRect и shape, так как эти функции ничего не знают о том, что я рисую в paint.

Подскажите, пожалуйста, как решить эту проблему, как заставить графические элементы жить в системе координат сцены, но чтобы их размеры оставались неизменны при масштабировании вида области отображения? Естественно, что с одной сценой могут работать несколько видов.
« Последнее редактирование: Август 07, 2011, 15:35 от iroln » Записан
iroln
Гость
« Ответ #1 : Август 07, 2011, 21:40 »

В общем на данный момент смог залепить такой костыль:

1. Для элементов класса ImageRectItem устанавливаю флаг ItemIgnoresTransformations в true
2. Для элементов класса ImageRectMarkerItem устанавливаю флаг ItemIgnoresTransformations в false и свойство transformOriginPoint в значение marker_rect.center(), позиция маркера обновляется всякий раз при обновлении родительской рамки (ImageRectItem)
3. В классе ImageRectItem переопределяю метод setScale() и в этом методе для всех потомков (ручек-маркеров) устанавливаю scale = 1 / parent_scale
4. В своём классе ImageView, который наследуется от QGraphicsView, в методе, который изменяет масштаб отображения, для всех элементов класса ImageRectItem обновляю scale, вызывая их метод setScale() и передавая в него текущий масштаб вида.

По такой схеме всё работает правильно, но мне не нравится то, что приходится в классе вида взаимодействовать с графическими элементами определённого типа, изменять их свойства. Проверка типа элементов и установка трансформаций занимает время и вообще, всё это как-то криво, как мне кажется. Хотя бы потому, что такая схема не работает с несколькими видами. Так как трансформации инструмента, сделанные в одном виде, делают неверным отображение этого же инструмента в другом виде, где может быть задан совсем другой масштаб.

Должен быть более красивый и верный способ сделать то, что я хочу. Гуру работы с графикой в Qt, подскажите путь, куда двигаться. С этими трансформациями можно мозги покалечить. Улыбающийся

« Последнее редактирование: Август 07, 2011, 21:58 от iroln » Записан
iroln
Гость
« Ответ #2 : Август 08, 2011, 09:48 »

Проблему решил. Не совсем, конечно, так как хотелось в идеале, но дальше возиться с этим нет ни желания ни времени. Лень уже описывать как именно проблема была решена, но если кому-то вдруг будет интересно решение, или кто-то столкнётся с подобными трудностями, пишите в личку.

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

Сообщений: 11445


Просмотр профиля
« Ответ #3 : Август 08, 2011, 10:14 »

Понял одно, хвалёная гибкость и универсальность тулкитов вроде Qt может выйти боком и вообще ставится под сомнение, когда нужно сделать что-то не совсем стандартное.
Ну "нестандартным" то что Вы хотите никак не назовешь  Улыбающийся
Я столкнулся с похожими трудностями: хотел рисовать одну модель в неск окнах (виды спереди, сбоку и.т.п). Логично было бы иметь один QGraphicsItem, одну QGraphicsScene и нужное кол-во QGraphicsView - но это не проходит из-за boundingRect  Плачущий

Иногда молчание объясняется не тем что, мол, никому неинтересно, а тем что хорошего ответа просто нет  Улыбающийся 
Записан
GreatSnake
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2921



Просмотр профиля
« Ответ #4 : Август 08, 2011, 11:05 »

Я столкнулся с похожими трудностями: хотел рисовать одну модель в неск окнах (виды спереди, сбоку и.т.п). Логично было бы иметь один QGraphicsItem, одну QGraphicsScene и нужное кол-во QGraphicsView - но это не проходит из-за boundingRect
Мде, как говорится - "хотеть не вредно" Грустный
Тоже не раз приходило такое желание, но, видимо, MVC в случае с QGraphics не работает. Максимально, что удалось из этого выжать - это zoom-view.
Записан

Qt 5.11/4.8.7 (X11/Win)
GreatSnake
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2921



Просмотр профиля
« Ответ #5 : Август 08, 2011, 15:02 »

Проблему решил. Не совсем, конечно, так как хотелось в идеале, но дальше возиться с этим нет ни желания ни времени. Лень уже описывать как именно проблема была решена, но если кому-то вдруг будет интересно решение, или кто-то столкнётся с подобными трудностями, пишите в личку.

Понял одно, хвалёная гибкость и универсальность тулкитов вроде Qt может выйти боком и вообще ставится под сомнение, когда нужно сделать что-то не совсем стандартное.
На самом деле, имхо, вы просто не разобрались до конца и делается это всё довольно-таки просто. Вот работающий пример:
Код
C++ (Qt)
#include <QApplication>
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QGraphicsRectItem>
#include <QMouseEvent>
 
class RubberItem : public QGraphicsRectItem
{
public:
RubberItem( const QRectF& r, QGraphicsItem* parent = 0 )
: QGraphicsRectItem( r, parent )
{
QColor clr( Qt::blue );
setPen( clr );
clr.setAlphaF( .1 );
setBrush( clr );
}
virtual ~RubberItem() {}
QRectF boundingRect() const
{
qreal es = grip_size_ / 2;
return rect().adjusted( -es, -es, es, es );
}
void paint( QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget = 0 )
{
QGraphicsRectItem::paint( painter, option, widget );
 
QMatrix m = painter->matrix();
QRectF irect( QRectF( QPointF( 0, 0 ), m.mapRect( rect() ).size() ) );
 
qreal gs = grip_size_;
QRectF r( -gs / 2, -gs / 2, gs, gs );
r.translate( m.map( QPointF( 0, 0 ) ) );
 
QColor clr( painter->brush().color() );
clr.setAlphaF( 1 );
painter->setBrush( clr );
 
painter->resetTransform();
 
painter->drawRect( r.translated( irect.topLeft() ) );
painter->drawRect( r.translated( QPointF( irect.width() / 2, 0 ) ) );
painter->drawRect( r.translated( irect.topRight() ) );
painter->drawRect( r.translated( QPointF( 0, irect.height() / 2 ) ) );
painter->drawRect( r.translated( irect.bottomLeft() ) );
painter->drawRect( r.translated( QPointF( irect.width(), irect.height() / 2 ) ) );
painter->drawRect( r.translated( irect.bottomRight() ) );
painter->drawRect( r.translated( QPointF( irect.width() / 2, irect.height() ) ) );
}
Qt::WindowFrameSection frameSectionAt( const QPointF& p ) const
{
QRectF irect( rect() );
 
qreal gs = grip_size_;
QRectF r( -gs / 2, -gs / 2, gs, gs );
r.translate( pos() );
 
Qt::WindowFrameSection section = Qt::NoSection;
if( r.translated( irect.topLeft() ).contains( p ) )
section = Qt::TopLeftSection;
else if( r.translated( QPointF( irect.width() / 2, 0 ) ).contains( p ) )
section = Qt::TopSection;
else if( r.translated( irect.topRight() ).contains( p ) )
section = Qt::TopRightSection;
else if( r.translated( QPointF( irect.width(), irect.height() / 2 ) ).contains( p ) )
section = Qt::RightSection;
else if( r.translated( irect.bottomRight() ).contains( p ) )
section = Qt::BottomRightSection;
else if( r.translated( QPointF( irect.width() / 2, irect.height() ) ).contains( p ) )
section = Qt::BottomSection;
else if( r.translated( irect.bottomLeft() ).contains( p ) )
section = Qt::BottomLeftSection;
else if( r.translated( QPointF( 0, irect.height() / 2 ) ).contains( p ) )
section = Qt::LeftSection;
return section;
}
 
static qreal gripSize() { return grip_size_; }
 
private:
static const qreal grip_size_ = 5;
};
 
class GraphicsView : public QGraphicsView
{
public:
GraphicsView( QWidget* p = 0 ) : QGraphicsView( p )
{
viewport()->setMouseTracking( true );
}
void resizeEvent( QResizeEvent* e )
{
fitInView( scene()->sceneRect() );
}
void mouseMoveEvent( QMouseEvent* e )
{
QGraphicsView::mouseMoveEvent( e );
QList< QGraphicsItem* > il = items(
e->pos().x(), e->pos().y(), 1, 1, Qt::IntersectsItemBoundingRect );
RubberItem* ri;
if( !il.isEmpty() &&
( ri = dynamic_cast< RubberItem* >( il.first() ) ) )
updateRubberItemCursor( ri, e->pos() );
else
viewport()->unsetCursor();
}
void updateRubberItemCursor( RubberItem* ri, const QPoint& cpos )
{
switch( ri->frameSectionAt( mapToScene( cpos ) ) )
{
case Qt::TopLeftSection:
case Qt::BottomRightSection:
viewport()->setCursor( Qt::SizeFDiagCursor );
break;
case Qt::TopRightSection:
case Qt::BottomLeftSection:
viewport()->setCursor( Qt::SizeBDiagCursor );
break;
case Qt::TopSection:
case Qt::BottomSection:
viewport()->setCursor( Qt::SizeVerCursor );
break;
case Qt::LeftSection:
case Qt::RightSection:
viewport()->setCursor( Qt::SizeHorCursor );
break;
default:
viewport()->unsetCursor();
break;
}
}
};
 
int main( int argc, char** argv )
{
QApplication app( argc, argv );
 
GraphicsView gv;
QGraphicsScene scene;
 
gv.setScene( &scene );
 
scene.setSceneRect( 0, 0, 300, 300 );
RubberItem item( QRectF( 0, 0, 200, 200 ) );
scene.addItem( &item );
item.setPos( 50, 50 );
 
gv.show();
 
return app.exec();
}
 

PS: исправил GraphicsView::mouseMoveEvent() чтобы при поиске элемента учитывался его boundingRect().
« Последнее редактирование: Август 09, 2011, 15:49 от GreatSnake » Записан

Qt 5.11/4.8.7 (X11/Win)
iroln
Гость
« Ответ #6 : Август 08, 2011, 16:56 »

GreatSnake, благодарю за пример!

Но у меня почему-то в этом примере не работает ресайз, пока не разбирался почему. Очень трудно попасть мышкой в grips, потому что нужно суметь прицелиться в точку вершины прямоугольника (e->pos()), так как grips по сути в этом примере виртуальные, они просто рисуются в методе paint.

В моём коде я сделал ручки-маркеры (grips) в виде графических объектов, это позволяет задавать им нужный boundingRect для лучшего попадания мышкой. Если бы ещё решить проблему с трансформациями, а именно, я хочу, чтобы событие ресайза активировалось не только когда курсор находится над ручкой-маркером, но и по всем границам рамки. Раньше у меня это было сделано как раз через boundingRect и shape ручек-маркеров, но после переделки кода, ручки маркеры стали игнорировать трансформации, поэтому так сделать уже не получается, так как родитель трансформации не игнорирует и без знания о масштабе вида не удастся вычислить правильные boundingRect.

Я могу убрать всё лишнее из моей реализации, выложить её сюда, чтобы стало более понятно, если это кому-то нужно. Ещё одна причина, по которой я стараюсь не переопределять кричные к скорости методы, такие как paint, boundingRect и т.д., это то, что я пишу приложение на PySide.
Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #7 : Август 08, 2011, 21:23 »

На самом деле, имхо, вы просто не разобрались до конца и делается это всё довольно-таки просто. Вот работающий пример:
Не могу назвать такой пример простым и ясным Улыбающийся Написали Вы чисто, хорошо, но капитальный пробой в архитектуре QGraphics от этого, пожалуй, еще более заметен. Мол, "если чего не достает, сделаем руками!" - так тут много чего придется делать. Вычисление ресайза уже не мед. А если родительский rect item перекрыт (полностью или частично)? Какие "ручки" бум рисовать а какие нет? Это только так, навскидку - а там "чем дальше в лес тем больше дров", попрет из всех щелей.

На мой взгляд лучше все вспомогательные ручки держать как item'ы. Да. приходится смириться с тем что для каждого QGraphicsView надо иметь свои QGraphicsItem'ы. Да, это у них сделано мудаковато, но в конце концов "nothing is perfect", нельзя надеяться что фреймворк удовлетворит все наши запросы/желания - к этому нужно спокойно относиться  Улыбающийся
Записан
iroln
Гость
« Ответ #8 : Август 08, 2011, 22:17 »

На мой взгляд лучше все вспомогательные ручки держать как item'ы. Да. приходится смириться с тем что для каждого QGraphicsView надо иметь свои QGraphicsItem'ы. Да, это у них сделано мудаковато, но в конце концов "nothing is perfect", нельзя надеяться что фреймворк удовлетворит все наши запросы/желания - к этому нужно спокойно относиться  Улыбающийся
А как это, для каждого QGraphicsView свои QGraphicsItem'ы? Ведь нельзя же вроде item'ы на одном виде скрыть, на другом показать. Это тогда надо и сцену для каждого представления свою собственную. Но тогда вся архитектура модель-представление в QGraphics летит к чертям. Честно говоря, на мой взгляд, QGraphics, конечно, лучше чем QCanvas, но что-то в архитектуре всё же не так, потому что шаг в сторону от колеи примеров из документации, обязательно вылазит какая-нибудь бяка.

Вот как пример: Вся эта куча трансформаций и систем координат. В QPainter своя матрица трансформации, в QGraphicsView своя матрица трансформации, у QGraphicsItem тоже есть своя матрица трансформации, а толку? Я понимаю, что это сделано для гибкости, но при всей этой гибкости многие вещи всё равно сделать не получается. Вопросов у людей по поводу QGraphicsScene /QGraphicsView тоже куча, потому что использование этого инструмента нельзя назвать тривиальным и даже удобным. Архитектура QGraphicsScene/QGraphicsView появилась в Qt 4.2, но мне кажется, что инструментарий ещё сырой, даже API местами не согласовано и слишком перегружено.

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

« Последнее редактирование: Август 08, 2011, 22:20 от iroln » Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #9 : Август 09, 2011, 09:35 »

А как это, для каждого QGraphicsView свои QGraphicsItem'ы? Ведь нельзя же вроде item'ы на одном виде скрыть, на другом показать. Это тогда надо и сцену для каждого представления свою собственную.
Да, ну и что здесь такого уж плохого? У Вас уже есть необходимость в 2 и более видах одной сцены? Если пока нет, то решаете "немасштабируемость" компенсацией масштаба.и живете спокойно.

Я лично не вижу зачем иметь 2 и более вида "с теми же самыми айтемами". Гораздо более реален напр такой случай: в одном окне - все "объекты", а в другом - только один крупным планом. Так все равно с одной сценой не протолкнетесь.

Но тогда вся архитектура модель-представление в QGraphics летит к чертям.
То чего нет - не летает  Улыбающийся
Записан
iroln
Гость
« Ответ #10 : Август 09, 2011, 09:53 »

Я лично не вижу зачем иметь 2 и более вида "с теми же самыми айтемами". Гораздо более реален напр такой случай: в одном окне - все "объекты", а в другом - только один крупным планом. Так все равно с одной сценой не протолкнетесь.
Ну почему, я думаю это вполне возможно. В первом окне отображаются все объекты, а для второго окна можно задать нужные параметры масштаба и sceneRect и отображать нужный кусок сцены. Проблема в том, что с одной сценой не получится настроить различную интерактивность отдельных элементов в каждом из видов и скрывать/показывать отдельные элементы, это и портит всю малину. Улыбающийся

То чего нет - не летает  Улыбающийся
Видимо так и есть. Улыбающийся
Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #11 : Август 09, 2011, 10:02 »

Ну почему, я думаю это вполне возможно. В первом окне отображаются все объекты, а для второго окна можно задать нужные параметры масштаба и sceneRect и отображать нужный кусок сцены.
Да, здесь придется синхронизировать 2 или более сцен, но так ли уж это страшно? Ну дали каждому айтему указатель на "корневые" данные (общие для всех сцен), связали айтемы в разных сценах сигналами для их синхронного перемещения, выбора, удаления. Зато руки свободны, что хотите - то и делаете
Записан
iroln
Гость
« Ответ #12 : Август 09, 2011, 10:44 »

Igors, согласен с Вами. Мысли здравые. Похоже, так и буду делать. Улыбающийся
Записан
GreatSnake
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2921



Просмотр профиля
« Ответ #13 : Август 09, 2011, 11:12 »

Но у меня почему-то в этом примере не работает ресайз, пока не разбирался почему.
Дык ресайз там просто не реализован)

Цитировать
Очень трудно попасть мышкой в grips, потому что нужно суметь прицелиться в точку вершины прямоугольника (e->pos()),
А кто мешает в frameSectionAt() увеличить границы.

Цитировать
так как grips по сути в этом примере виртуальные, они просто рисуются в методе paint.
А что если grips-ы будут самостоятельными айтемами что-то изменится Непонимающий

Igors, согласен с Вами. Мысли здравые. Похоже, так и буду делать. Улыбающийся
Зачем так сложно Непонимающий Чем не устраивает мой вариант?
Что вы выиграете от разбивки grips-ов на отдельные элементы? Да ещё создавая кучу сцен.
Не понимаю. Зачем из простой задачи устраивать такие сложности?

Записан

Qt 5.11/4.8.7 (X11/Win)
GreatSnake
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2921



Просмотр профиля
« Ответ #14 : Август 09, 2011, 11:32 »

Не могу назвать такой пример простым и ясным Улыбающийся Написали Вы чисто, хорошо, но капитальный пробой в архитектуре QGraphics от этого, пожалуй, еще более заметен. Мол, "если чего не достает, сделаем руками!" - так тут много чего придется делать. Вычисление ресайза уже не мед.
Абсолютно согласен. Только вот, имхо, на данный момент это самый простой вариант. Иначе одновременно рисовать с трансформациями и без не получится.

Цитировать
А если родительский rect item перекрыт (полностью или частично)? Какие "ручки" бум рисовать а какие нет? Это только так, навскидку - а там "чем дальше в лес тем больше дров", попрет из всех щелей.
Не понял насчёт "родительский rect item". Его ведь нет. Всё делается в рамках одного айтема. Изучая исходники Qt ни разу не видел обеспокоенности авторов об излишней отрисовке. Включите перед запуском любого приложения переменную среды QT_FLUSH_UPDATE и сами всё увидите) Тем более что при "глобальном" double-buffering это не так актуально. Но если уж очень хочется,  то никто нам не мешает получить от painter-a clipRegion() и проверять вхождение этих grips в "exposures".

Цитировать
На мой взгляд лучше все вспомогательные ручки держать как item'ы. Да. приходится смириться с тем что для каждого QGraphicsView надо иметь свои QGraphicsItem'ы.
А вот это, имхо, самое тупиковое решение. Айтем должен быть один. Если отрисовка вьюх должна отличаться по содержанию нужно такую отрисовку переность в QGraphicsView::drawForeground() или в перегруженных QGraphicsItem::paint() проверать в какой вьюхе делается отрисовка и делать return для ненужной.
Записан

Qt 5.11/4.8.7 (X11/Win)
Страниц: [1] 2 3   Вверх
  Печать  
 
Перейти в:  


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