Rysowanie po QGraphicsView

0

Witam!

Chciałbym się was zapytać czy mój tok myślenia jest dobry i co mogę zrobić aby zrealizować zamierzony efekt. Mam QGraphicsView, do którego ładuje zdjęcie z dysku a następnie powinienem móc myszką na tym zdjęciu zaznaczać pewne kluczowe punkty - markery. W tym celu stworzyłem sobie nową klasę pt. Marker, w której tworze obszar (nową scenę) dla myszki i dodaje ją poleceniem addWidget do layoutu dla QGraphicsView, jednak nie potrafię sobie poradzić z tym aby do tej klasy przekazać już istniejącą scenę z obrazem, na którym będzie można zaznaczyć tylko i wyłącznie dwa punkty, i odczytać ich współrzędne.

Moja klasa Marker.cpp zawierająca konstruktor domyślny który działa (bo nakłada nową scenę) i konstruktor który dostaje scenę ale nie potrafi jej użyć

#include "mainwindow.h"
#include "marker.h"
#include <QPointF>
#include <QMessageBox>
#include <QGraphicsView>
#include <QGraphicsScene>

Marker::Marker(QWidget *parent) :
    QGraphicsView(parent)
{
    scena = new QGraphicsScene();
    this->setSceneRect(50, 50, 320, 320);
    this->setScene(scena);
}

Marker::Marker(QGraphicsScene scenaDlaMyszy, QWidget *parent) :
    QGraphicsView(parent)
{
    scena.update(scenaDlaMyszy);
    this->setSceneRect(0, 0, 421, 421);
    this->updateScene(scena);
}

Marker::~Marker()
{

}

void Marker::mousePressEvent(QMouseEvent * e)
{
    int i = 0;
    if(i <= 2)
    {
        double rad = 1;
        QPointF punkt = mapToScene(e->pos());
        scena->addEllipse(punkt.x()-rad, punkt.y()-rad, rad*5.0, rad*5.0, QPen(), QBrush(Qt::SolidPattern));
        i += 1;
    }
}

W tej chwili wygląda to tak że po załadowaniu obrazu do tej kontrolki nakładany jest na niego kwadrat (nowa scena tworzona w klasie Marker) w którym można zaznaczać punkty.

Kod w pliku okna głównego który wypełnia QGraphicsView:

nazwaZdjecia1 = QFileDialog::getOpenFileName(this, tr("Otwórz plik"), "C:/", tr("Obrazy (*.jpg *.png *.bmp)"));
    QFile ladowanyPlik(nazwaZdjecia1);
    sciezka1 = ladowanyPlik.fileName();
    if(!sciezka1.isEmpty()){
        QImage obraz(sciezka1);
        obraz.scaled(QSize(421,421),Qt::IgnoreAspectRatio,Qt::FastTransformation);
        update();
        resize(421, 421);
        QGraphicsScene *scena = new QGraphicsScene(ui->graphicsViewZdjecie1);
        QPixmap mapaPikseli(nazwaZdjecia1);
        scena->addPixmap(mapaPikseli);
        QGridLayout *oknoObrazu1 = new QGridLayout(ui->graphicsViewZdjecie1);
        oknoObrazu1->addWidget(new Marker()); //opcja działająca ale niewłaściwie
        //oknoObrazu1->addWidget(new Marker(scena)); //opcja niedziałająca z powodu braku sceny
        ui->graphicsViewZdjecie1->setScene(scena);
        ui->graphicsViewZdjecie1->fitInView(scena->itemsBoundingRect(),Qt::KeepAspectRatio);
        ui->graphicsViewZdjecie1->show();
    }

Czy idąc tym tokiem myślenia jest możliwość odczytania współrzędnych nanoszonych punktów względem obrazu i co muszę uczynić albo jak się doedukować aby osiągnąć takie założenia? :)

0

Tak też teraz myślę że problem może tkwić w pliku nagłówkowym klasy Marker, który wygląda następująco:

#ifndef MARKER_H
#define MARKER_H

#include <QGraphicsView>
#include <QGraphicsScene>
#include <QGraphicsEllipseItem>
#include <QMouseEvent>

class Marker : public QGraphicsView
{
    Q_OBJECT

public:
    explicit Marker(QWidget *parent = 0);
    explicit Marker(QGraphicsScene scenaDlaMyszy, QWidget *parent = 0);
    ~Marker();

private slots:
    void mousePressEvent(QMouseEvent * e);

private:
    QGraphicsScene *scena;
};

#endif // MARKER_H

z tego względu że musze tutaj zadeklarować wskaźnik scena. Co wy o tym myślicie? Gdzie robię błąd? Może jakieś podpowiedzi albo wypraktykowane rozwiązania jesteście w stanie przytoczyć?

0

Źle! QGraphicsView to jest QWidget do pokazywania sceny zawierającej QGraphicsItems.
Jeśli używasz QGraphicsView to twoje elementy UI powinny się znaleźć wewnątrz QGraphicsScene.

Może lepiej stosuj zwykłe QWidgety skoro nie rozumiesz o co chodzi zQGraphicsView.

0

Staram się to wszystko właśnie zrozumieć :/ Środowisko i język mam niestety narzucone przez co może zadaje głupie pytania ale jeszcze jakoś nie przyzwyczaiłem się do tego a projekt muszę doprowadzić do końca. Za dużo mam teraz już rzeczy zrobionych żeby modyfikować swoje ui tak, że może znajdzie się osoba, która łopatologicznie mi to wytłumaczy bądź poda jakiś przykład jak to powinno być wykonane, gdyż w stosunku do całego projektu jest to dość kluczowa sprawa i w tym wypadku QGraphicsView musi zostać użyty.

0

A może moje myslenie jest błędne i rysowanie po obrazie wyświetlonym w QGraphicsView trzeba wykonać inaczej? Bardzo was proszę o jakąkolwiek podpowiedź bo bez tego nie jestem w stanie już ruszyć z dalszymi etapami projektu. Jest to mój pierwszy projekt w Qt i idzie to troche opornie, a chciałbym doprowadzić go do szczęśliwego końca :)

1

Z opisu funkcjonalności wynika, że QGraphicsView jest przerostem formy nad treścią. Robiąc to na QWidgetach zaoszczędziłbyś sporo czasu.

Jeśli jednaj już musisz, to nie dziedzicz po QGraphicsView, jest to ci zupełnie niepotrzebne.
Do zwykłego QGraphicsView dodaj zwykłe QGraphicsScene, a do sceny dodawaj odpowiednie QGraphicsItem.
Niestety zestaw gotowych QGraphicsItem jest dość ubogi i dużo trzeba zrobić samemu.

W pierwszym podejściu zrobiłbym tak (kod minimum):

class MarkersEditor : public QGraphicsObject {
     Q_OBJECT

     QGraphicsPixmapItem *pixmapItem;
public:
    MarkersEditor(QGraphicsItem * parent = 0)
         : QGraphicsObject(parent)
    {
         pixmapItem = new QGraphicsPixmapItem(this);
         pixmapItem->setShapeMode(QGraphicsPixmapItem::BoundingRectShape);
         setFlags(ItemHasNoContents);
         setAcceptedMouseButtons(Qt::LeftButton);
    }

   QPainterPath shape() const {
        return pixmapItem->shape();
   }

   void setPixmap(const QPixmap & pixmap) {
        pixmapItem->setPixmap(pixmap);
   }

   void mousePressEvent(QGraphicsSceneMouseEvent * event) {
         // by default it is accepted
   }

   void mouseReleaseEvent(QGraphicsSceneMouseEvent * event) {
         QRectF dotPos(event->pos(), QSizeF(2,2));
         dotPos.translate(-1,-1);
         QGraphicsEllipseItem * dot = new QGraphicsEllipseItem(dotPos, this);
         dot->setBrush(QBrush(Qt::red));
   }
};

Nie mam pewności czy to działa, bo pisane bez testowania, a nie używałem QGraphicsView dość długo.
Oczywiście można tu dużo poprawić, dodać emitowanie sygnałów, zdefiniować properties, poprawić funkcjonalność, zrobić model danych z informacją, gdzie są punkty kontrolne, itp itd.
Przynajmniej masz punkt zaczepienia.

0

Zgodnie z Twoimi wskazówkami zrobiłem sobie klase MarkerEditor, którą dołączam jako Item do sceny w kodzie głównego okna, tam gdzie wyświetlam zdjęcie w QGraphicsView, a wygląda to następująco:

QGraphicsScene *scena = new QGraphicsScene(ui->graphicsViewZdjecie1);
        MarkerEditor *marker = new MarkerEditor();
        QGraphicsPixmapItem mapaPikseli(nazwaZdjecia1);
        marker->pixmapItem = mapaPikseli;
        scena->addItem(mapaPikseli);
        scena->addItem(marker);

Kompilator wywala jednak błąd, że klasa MarkerEdit jest klasą abstrakcyjną i nie można utworzyć jej instancji z powodu jej elementów (też abstrakcyjnych):

"MarkerEditor" : nie moľna utworzy† instancji klasy abstrakcyjnej
z powodu nast©pujĄcych element˘w czonkowskich:
"QRectF QGraphicsItem::boundingRect(void) const": jest abstrakcyjna
c:\qt\qt5.0.2\5.0.2\msvc2012_64\include\qtwidgets\qgraphicsitem.h(328): zobacz deklaracj© "QGraphicsItem::boundingRect"
"void QGraphicsItem::paint(QPainter *,const QStyleOptionGraphicsItem *,QWidget *)": jest abstrakcyjna
c:\qt\qt5.0.2\5.0.2\msvc2012_64\include\qtwidgets\qgraphicsitem.h(348): zobacz deklaracj© "QGraphicsItem::paint"

Po drugie (pewnie ja coś robie źle :/) kompilator wywala błąd, ze względu na brak możliwości konwertowania pixmapy:

błąd:C2440: "=" : nie moľna konwertowa† z "QGraphicsPixmapItem" na "QGraphicsPixmapItem *"
Nie jest dost©pny ľaden operator konwersji zdefiniowany przez uľytkownika, kt˘ry m˘gby wykona† t© konwersj© lub operator nie moľe zosta† wywoany

błąd:C2664: "QGraphicsScene::addItem" : nie moľna konwertowa† parametru 1 z "QGraphicsPixmapItem" na "QGraphicsItem *"
Nie jest dost©pny ľaden operator konwersji zdefiniowany przez uľytkownika, kt˘ry m˘gby wykona† t© konwersj© lub operator nie moľe zosta† wywoany

Myślałem ze to może błąd linkera ale ani Rebuild ani qmake nie rozwiązuje tego problemu a czegoś takiego jak addQPixmapItem nie ma nawet w dokumentacji (z tego co się doedukowałem addItem powinno obsłużyć tego typu obiekt)

Czyli, w czym jest problem?

0

Drobny update ze względu na moją niedoskonałość :/

QGraphicsScene *scena = new QGraphicsScene(ui->graphicsViewZdjecie1);
        MarkerEditor *marker = new MarkerEditor();
        QPixmap mapaPikseli(nazwaZdjecia1);
        marker->setPixmap(mapaPikseli);
        scena->addPixmap(mapaPikseli);
        scena->addItem(marker);

Teraz mój kod wygląda tak, i wszystko było by ok, gdyby nie to że kompilatorowi dalej nie podoba się klasa abstrakcyjna MarkerEditor :/ Co powinienem z tym zrobić? Próbowałem już różnych deklaracji obektu tej klasy (przez "new" i bez "new") ale cały czas to samo. Macie jakieś pomysły?

Zalezy mi na tym aby w drugiej kolejności odczytać współrzędne naniesoinych punktów, aczkolwiek mam nadzieje że z tym sobie już poradze jakoś.

1

to są podstawy programowania obiektowego w C++, jeśli klasa nie może być utworzona bo jest abstrakcyjna, znaczy, że należy dodać brakujące metody, które powinny być nadpisane według projektanta biblioteki.
Jako, że ustawiam flagę ItemHasNoContents by poinformować o tym, że ten item nie zawiera bezpośrednio treści (nic nie rysuje), oraz że kompilator woła o implementację metod, które są właśnie związane z rysowaniem, to należy dodać ich puste implementacje (i tak nie powinny być wywołane):

QRectF boundingRect() const {
      return QRectF();
}

void paint(QPainter *,const QStyleOptionGraphicsItem *,QWidget *) {
    // no implementation is needed
}
0

Nie chce się żadnym stopniu usprawiedliwiać, ale tak jak wspominałem jest to mój pierwszy projekt w Qt, od razu taka wielka kobyła, i będąc nauczony schematów z VS (w którym posiadam kilkuletnie doświadczenie) po prostu nie zdawałem sobie sprawy i nie pamiętałem o tym że tam jeśli tworzy się klasy abstrakcyjne to metody, które są metodami abstrakcyjnymi generowane są automatycznie w kodzie z pustym "ciałem" i w takim wypadku programista nie zwraca na nie uwagi budując klasy w sposób "normalny". Wystarczyło wspomnieć o obowiązku implementacji pustych metod abstrakcyjnych. Tak, wiem że powinienem o nich pamiętać... ale cóż - jestem tylko człowiekiem.

Wszystko spoko, i na dobą sprawe wydawało by się, że powinno to działać bo program się kompiluje, jednak zdarzenia naciśnięcia myszy na obraz, jak gdyby w dalszym ciągu nie są obsługiwane. Idąc po kolei - w formie głównym mam juz taki kod:

QGraphicsScene *scena = new QGraphicsScene(ui->graphicsViewZdjecie1);
        MarkerEditor *marker = new MarkerEditor();
        QPixmap mapaPikseli(nazwaZdjecia1);
        marker->setPixmap(mapaPikseli);
        //scena->addPixmap(mapaPikseli);
        scena->addItem(marker);

Tworzę scenę dla QGraphicsView, tworze Marker jako edytor na zdjęciu w myśl kodu MarkerEditor z posta wyżej, przypisuje mapę pikseli do markera i tą samą mapę przypisuje do sceny (nie muszę tego robić bo mapa zostaje wcześniej przypisana do klasy MarkerEditor, ale w niczym to nie przeszkadza - mogę to wyrzucić) tak aby obrazek się wyświetlał i aby można było po nim rysować, na końcu dodaje do sceny obiekt markera jako Item. Wszystko zgodnie ze wskazówkami użytkownika @MarekR22 a jednak nie ma reakcji na kliknięcie myszą na obrazie :/

mały update:

Może inaczej, czy kod, który mam w metodzie odpalanej podczas zwolnienia klawisza myszy jest prawidłowy:

 QRectF dotPos(event->pos(), QSizeF(2,2));
         dotPos.translate(-1,-1);
         QGraphicsEllipseItem * dot = new QGraphicsEllipseItem(dotPos, this);
         dot->setBrush(QBrush(Qt::red));

Tworze tutaj pozycje i samą kropkę która ma zostać naniesiona na piksmape, którą wcześniej pobieram z okna głównego funkcją setPixmap. Dlaczego o to pytam? Bo nie znalazłem żadnej funkcji w odniesieniu do piksmapy aby dodać do niej Item a mam wrażenie że stworzenie obiektu QGraphicsEllipseItem nie wystarczy, gdyż w dalszym ciągu QGraphicsView, nie reaguje na żadne kliknięcie.

0

Jakieś pomysły co do posta wyżej? Ja już kapituluje... :/

0

możesz spróbować tak (licząc na to, że dokumentacja jest niepełna i test na trafienie myszką najpierw sprawdza boundingRect):

QRectF boundingRect() const {
      return pixmapItem->shape()->boundingRect();
}

Wystaw zip-a, albo linka do repozytorium z kodem to na to spojrzę jak będę miał czas.

0

próbowałem tak już wcześniej i nic :/ Dlatego ciągne ten temat dalej :/

Link do repozytorium:
https://dl.dropboxusercontent.com/u/74090376/app.zip

0

Pomieszanie z poplątaniem, nie zagłębiałem się głęboko w kod, @MarekR22 na pewno prędzej zrozumie o co Ci chodzi. Obrazek na scenie ustawiasz mniej więcej tak:

scene->setBackgroundBrush(QPixmap(...));

Nie wiem po co tam musisz dalej rysować, ale to pewnie ma być jakiś kwadracik? Punkt? Obojętnie co to ma być musisz stworzyć swój własny QGraphicsSceneItem (albo QGraphicsItem? nie pamiętam), najpewniej możesz odziedziczyć z jakiś półgotowców jak np. QGraphicsLineItem albo innych (jest chyba jakiś półgotowiec do kwadracika, i chyba jeszcze jakiś do niestandardowych kształtów). Potem musisz go nanieść na tą scenę. Zapewnie dalej zapytasz jak obsługiwać eventy myszki, przekazywać współrzędne - nie wiem, @MarekR22 ma więcej cierpliwości do Ciebie więc pewnie Ci pomoże :)

0

Nie będę pytał jak obsługiwać eventy myszki @several, ale jak coś co inny robią podobnie i na chłopski rozum powinno działać a nie działa, a w internecie nie ma na to sprecyzowanej odpowiedzi to od tego są chyba fora aby zapytać innych, bardziej doświadczonych. Cały czas podkreślam, że jest to mój pierwszy i ostatni chyba projekt w obiektowym C++ bo jego składnia jest do reszty powalona i może pytam o bzdury (na codzień programując w Javie i w C#) ale nie zagłębiam się zbytnio w odmęty wielostronicowej dokumentacji bo nie mam na to czasu ani cierpliwości. Udostępniłem repozytoria, ten kto zobaczy ile tam jest rzeczy zrobionych, które zrobiłem bez pomocy forów, będzie wiedział, że to o czym piszę i udzielam się w postach to jest mały wycinek całego projektu tworzonego głównie po nocach, w którym sam musiałem nauczyć się wiele rzeczy i przede wszystkim odnaleźć się w języku i środowisku narzuconym odgórnie.

Zejdźcie ze mnie panowie, prosze bo to do niczego nie prowadzi. Też jestem programistą choć z niewielkim stażem i cały czas się uczę i chce to robić dalej, a najlepiej jest robić to na różnych ciekawych projektach, które mogą się przydać i na błędach, które sie popełnia podczas ich tworzenia aby w końcu osiągnąć zamierzony cel.

No i wielkie dzieki @MarekR22, że ma do mnie tyle cierpliwości i zechce mi jeszcze trochę pomóc, bo jego uwagi są nieocenione, dla mnie laika. Stawiam duże piwo i to nie jedno gdyby kiedyś było dane nam sie spotkać :)

1

Szczerze to wolałem sam napisać od nowa, niż ustalać co ty zrobiłeś źle.
Wygląda na to, że dostałeś komplet rad by wykonać to poprawnie (boundingRect ma zawierać obszar do klikania, czego nie ma opisanego w dokumentacji).
W moim projekcie wszystko działa, nawet za pomocą jednej dodatkowej linijki kropki można przesuwać.

0

Cuż mogę rzec... Dzieki, dzięki, dzieki jeszcze raz! Już sam powoli przestaje ogarniać to co stworzyłem i postaram sie to ujednolicić aby miało ręce i nogi, bo zmian w stosunku do wersji pierwszej było tyle, że już nawet nie liczę a z każdej jakaś linijka została, która nie przeszkadza a niepotrzebnie wprowadza zamieszanie... ehh, dużo się musze jeszcze chyba nauczyć :/

1 użytkowników online, w tym zalogowanych: 0, gości: 1