Qt - podstawy rysowania z wykorzystaniem klasy QPainter

Stronę tą wyświetlono już: 86 razy

Klasa QPainter umożliwia rysowanie np. po oknie programu czy kontrolki, jak również rysowanie po bitmapie a także drukowanie. Klasa ta zawiera zestaw metod umożliwiających rysowanie różnego typu prymitywów, takich jak:

  • punkty;
  • linie;
  • linie łamane;
  • wielokąty;
  • prostokąty;
  • elipsy;
  • tekst
  • i inne nie wymienione przeze mnie.

Rysowanie po oknie programu

Bardzo często do rysowania po oknie programu wykorzystuje się zdarzenie paintEvent. Zdarzenie to jest wywoływane gdy następuje konieczność odświeżenia (odrysowania) całego lub wyznaczonego obszaru okna programu (co wiąże się z optymalizacją). Program wywołuje to zdarzenie, gdy jakaś część okna wymaga odrysowania. Dzieje się tak np. przy zmianie rozmiaru okna, minimalizacji i maksymalizacji. Oto prosty przykład wykorzystania tego zdarzenia do narysowania okręgu:

Listing 1
  1. void MainWindow::paintEvent(QPaintEvent *event){
  2. QWidget::paintEvent(event);
  3. QPainter painter;
  4. painter.begin(this); // w tym momencie podpinam obiekt klasy painter pod okno programu, co oznacza rysowanie po nim
  5. painter.drawEllipse(QPoint(width() / 2, height() / 2), 100, 100); // rysowanie elipsy na środku okna
  6. painter.end(); // koniec rysowania po oknie
  7. }

Każda zmiana rozmiaru okna spowoduje jego odrysowanie co z kolei sprawi, że okrąg narysowany będzie zawsze rysował się na środku tegoż okna. Gdyby zrobić mały eksperyment i cały proces rysowania wykonać jednorazowo np. po kliknięciu w oknie programu lewym przyciskiem myszy to okazałoby się, że każde odświeżenie okna wymazywałoby tak narysowany element. Z tego względu proces rysowania powinien odbywać się pod zdarzeniem paintEvent, natomiast zapamiętywanie obiektów, które powinny być rysowane powinno być realizowane oddzielnie.

Rysowanie po obiekcie klasy QImage

Możliwe jest również rysowanie po utworzonym obiekcie klasy QImage. Oto przykład:

Listing 2
  1. #include "mainwindow.h"
  2. #include "ui_mainwindow.h"
  3. MainWindow::MainWindow(QWidget *parent) :
  4. QMainWindow(parent),
  5. ui(new Ui::MainWindow),
  6. brushLine(QColor(255,0,0)), // konstruktor obiektu klasy QBrush
  7. penLine(brushLine, 4) // konstruktor obiektu klasy QPen
  8. {
  9. ui->setupUi(this);
  10. image = new QImage(800, 300, QImage::Format_ARGB32); // tworzę bitmapę o rozmiarze 800 x 300 z 32 bitową głębią kolorów
  11. image->fill(QColor(255, 255, 255)); // wypełniam bitmapę kolorem białym
  12. paintOnImage = new QPainter; // tworzę obiekt klasy QPainter
  13. paintOnImage->begin(image); // podpinam grafikę pod obiekt klasy QPainter
  14. paintOnImage->setPen(penLine); // ustawiam pędzel rysowania
  15. }
  16. MainWindow::~MainWindow()
  17. {
  18. delete ui;
  19. paintOnImage->end(); // kończę rysowanko po bitmapie
  20. delete image; // usuwam bitmapę
  21. delete paintOnImage; // usuwam obiekt klasy rysującej po bitmapie
  22. }
  23. void MainWindow::paintEvent(QPaintEvent *event){
  24. QWidget::paintEvent(event);
  25. QPainter painter;
  26. painter.begin(this);
  27. painter.drawImage(0, 0, *image); // rysowanie utworzonej wcześniej bitmapy
  28. painter.end();
  29. }
  30. void MainWindow::mouseMoveEvent(QMouseEvent *event){
  31. QWidget::mouseMoveEvent(event);
  32. if(event->buttons() & Qt::LeftButton){ // jeżeli lewy przycisk mychy został wciśnięty to
  33. paintOnImage->drawLine(mousePos.x() , mousePos.y(), event->x(), event->y()); // rysuję linię od ostatnio odnotowanego punktu do bieżącego
  34. // zapamiętuję punkt
  35. mousePos.setX(event->x());
  36. mousePos.setY(event->y());
  37. repaint(); // odrysowuję okno
  38. }
  39. }
  40. void MainWindow::mousePressEvent(QMouseEvent *event){
  41. // trzeba zapamiętać punkt kliknięcia myszką
  42. mousePos.setX(event->x());
  43. mousePos.setY(event->y());
  44. }

Powyższy prosty przykład umożliwia rysowanie jednym pędzlem po bitmapie poprzez wciśnięcie i przytrzymanie lewego przycisku myszy wraz z przemieszczeniem kursora myszy. Jest to realizowane w zdarzeniu mouseMoveEvent. Sam proces rysowania bitmapy w oknie programu jest z kolei realizowany w zdarzeniu paintEvent. Warto dodać, że domyślnie każda kontrolka wywołuje zdarzenie mouseMoveEvent tylko wtedy, gdy któryś z przycisków myszy zostanie wciśnięty. Stan ten można zmodyfikować korzystając z metody setMouseTracking wchodzącej w skład każdej kontrolki czy też każdego okna programu.

Qt Creator - przykład rysowania po bitmapie
Rys. 1
Qt Creator - przykład rysowania po bitmapie

Metody rysujące klasy QPainter

Oto lista metod rysujących wchodzących w skład klasy QPainter:

  • drawArc - rysuje łuk eliptyczny:

    Listing 3
    1. painter.drawArc(
    2. 100, // położenie lewej krawędzi prostokąta opisującego elipsę na osi x
    3. 100, // położenie lewej krawędzi prostokąta opisującego elipsę na osi y
    4. 50, // długość prostokąta opisującego elipsę
    5. 50, // wysokość prostokąta opisującego elipsę
    6. 20 * 16, // kąt początkowy wyrażony w 1 / 16 części stopnia jako liczba całkowita (int)
    7. 90 * 16 // kąt przyrostu łuku wyrażony w 1 / 16 części stopnia jako liczba całkowita (int)
    8. );
  • drawChord - rysuje odcinek elipsy:

    Listing 4
    1. painter.drawChord(
    2. 100, // położenie lewej krawędzi prostokąta opisującego elipsę na osi x
    3. 100, // położenie lewej krawędzi prostokąta opisującego elipsę na osi y
    4. 50, // długość prostokąta opisującego elipsę
    5. 50, // wysokość prostokąta opisującego elipsę
    6. 20 * 16, // kąt początkowy wyrażony w 1 / 16 części stopnia jako liczba całkowita (int)
    7. 90 * 16 // kąt przyrostu odcinka wyrażony w 1 / 16 części stopnia jako liczba całkowita (int)
    8. );
  • drawConvexPolygon - rysuje wielokąt wykorzystując tablicę punktów:

    Listing 5
    1. static const QPointF points[4] = {
    2. QPointF(10.0, 10.0),
    3. QPointF(110.0, 110.0),
    4. QPointF(110.0, 10.0),
    5. QPointF(10.0, 110.0)
    6. };
    7. painter.drawConvexPolygon(
    8. points, // tablica punktów
    9. 4 // liczba początkowych punktów do uwzględnienia przy rysowaniu
    10. );

    Można również wykorzystać obiekt klasy QPolygon lub QPolygonF do rysowania wielokątów.

  • drawEllipse - rysuje elipsę (a nawet i koło):

    Listing 6
    1. painter.drawEllipse(
    2. 100, // położenie lewej krawędzi prostokąta opisującego elipsę na osi x
    3. 100, // położenie lewej krawędzi prostokąta opisującego elipsę na osi y
    4. 50, // długość prostokąta opisującego elipsę
    5. 100 // wysokość prostokąta opisującego elipsę
    6. );
  • drawGlyphRun - rysowanie obiektu klasy QGlyphRun dającego dostęp do znaku danej czcionki;

  • drawImage - rysowanie bitmapy np. obiektu klasy QImage:
    Listing 7
    1. QRectF target(10.0, 20.0, 80.0, 60.0); // prostokąt docelowy
    2. QRectF source(0.0, 0.0, 70.0, 40.0); // prostokąt wyciąganego fragmentu grafiki
    3. QImage image(":/images/myImage.png"); // ładowanie grafiki z zasobów programu
    4. painter.drawImage(target, image, source); // rysowanie
  • drawLine - rysowanie linii:

    Listing 8
    1. painter.drawLine(
    2. 10, // x1
    3. 10, // y1
    4. 300, // x2
    5. 300 // y2
    6. );
  • drawLines - rysuje linie;

  • drawPath - rysuje ścieżkę z możliwością wykorzystania krzywych Bezier-a:

    Listing 9
    1. QPainterPath path;
    2. path.moveTo(20, 80);
    3. path.lineTo(20, 30);
    4. path.cubicTo(80, 0, 50, 50, 80, 80);
    5. painter.drawPath(path);
  • drawPicture - rysuje obiekt klasy QPicture;

  • drawPie - rysuje wycinek elipsy:

    Listing 10
    1. painter.drawPie(
    2. 100, // położenie lewej krawędzi prostokąta opisującego elipsę na osi x
    3. 100, // położenie lewej krawędzi prostokąta opisującego elipsę na osi y
    4. 50, // promień na osi x
    5. 50, // promień na osi y
    6. 20 * 16, // kąt początkowy wyrażony w 1 / 16 części stopnia jako liczba całkowita (int)
    7. 90 * 16 // kąt przyrostu wyrażony w 1 / 16 części stopnia jako liczba całkowita (int)
    8. );
  • drawPixmap - rysuje obiekt klasy QPixmap;

  • drawPixmapFragments;

  • drawPoint - rysowanie punktu;

  • drawPoints - rysowanie punktów;

  • drawPolygon - rysowanie wielokąta:

    Listing 11
    1. static const QPointF points[4] = {
    2. QPointF(10.0, 10.0),
    3. QPointF(110.0, 110.0),
    4. QPointF(110.0, 10.0),
    5. QPointF(10.0, 110.0)
    6. };
    7. painter.drawPolygon(
    8. points, // tablica punktów
    9. 4 // liczba początkowych punktów do uwzględnienia przy rysowaniu
    10. );

    Można również wykorzystać obiekt klasy QPolygon lub QPolygonF do rysowania wielokątów.

  • drawPolyline - rysuje linię łamaną:
    Listing 12
    1. static const QPointF points[3] = {
    2. QPointF(10.0, 80.0),
    3. QPointF(20.0, 10.0),
    4. QPointF(80.0, 30.0),
    5. };
    6. painter.drawPolyline(points, 3);

    Można również wykorzystać obiekt klasy QPolygon lub QPolygonF do rysowania linii łamanej.

  • drawRect - rysowanie prostokąta;

  • drawRects - rysowanie prostokątów;

  • drawRoundedRect - rysowanie prostokąta z zaokrąglonymi narożnikami;

  • drawStaticText - rysowanie tekstu zapisanego w obiekcie klasy QStatiText;

  • drawText - rysuje tekst zapisany w obiekcie klasy QString;

  • fillPath - wypełnia ścieżkę wybranym deseniem będącym obiektem klasy QBrush;

  • fillRect - wypełnia prostokąt wybranym deseniem będącym obiektem klasy QBrush

Pędzle i desenie

Pędzle to obiekty klasy QPen, które opisują właściwości linii rysowanych przez obiekt klasy QPainter. W celu wykorzystania pędzla obiekt klasy QPainter musi wywołać metodę begin a następnie za pomocą metody setPen można ustawić bieżący pędzel rysowania. Każdy pędzel ma do dyspozycji podstawowy zestaw styli, które można wyświetlić za pomocą poniższego kodu:

Listing 13
  1. QPen pen(Qt::black); // tworzę czarny pędzel
  2. pen.setWidth(2); // ustawiam jego szerokość na 2 px
  3. for(int i = 0; i < Qt::CustomDashLine; i++){
  4. QLine line(10, 30 + 10 * i, 110, 30 + 10 * i); // tworzę obiekt QLine (linii)
  5. pen.setStyle(Qt::PenStyle(i)); // ustawiam styl pędzla
  6. painter.setPen(pen); // ustawiam pędzel
  7. painter.drawLine(line); // rysuję linię ustawionym stylem pędzla
  8. }

Wynik działania powyższego kodu pokazany został na poniższym rysunku.

Qt Creator - wyświetlone podstawowe style pędzli
Rys. 2
Qt Creator - wyświetlone podstawowe style pędzli:
  • Qt::NoPen - brak pędzla;
  • Qt::SolidLine - linia ciągła;
  • Qt::DashLine - przerywana długa;
  • Qt::DotLine - kropkowana;
  • Qt::DashDotLine - linia kropka;
  • Qt::DashDotDotLine - linia kropka kropka;

Istnieje jeszcze jeden styl Qt::CustomDashLine, który umożliwia ustawienie własnego stylu kreskowania w następujący sposób:

Listing 14
  1. QPen pen(Qt::black);
  2. pen.setWidth(2);
  3. pen.setStyle(Qt::CustomDashLine); // ustawienie stylu zdefiniowanego przez użytkownika
  4. pen.setDashPattern( // ta metoda ustawia wzorzec rysowanej linii
  5. {10, 2, 2, 2, 2, 2, 2, 2, 2, 2} // QVector, którego kolejne wartości określają na przemian długość odcinka, odstęp, długość odcinka ...
  6. );
  7. painter.setPen(pen);
  8. QLine line(10, 30, 210, 30);
  9. painter.drawLine(line);

Powyższy kod narysuje linię o odcinkach długości 10, 2, 2, 2, 2, 10, 2, 2... i odstępach równych 2.

Podobnie rzecz ma się z deseniami opisującymi sposób wypełnienia obiektów rysowanych. Desenie są obiektami klasy QBrush i tak jak w przypadku pędzli można dany deseń ustawić jako bieżący za pomocą metody setBrush obiektu klasy QPainter. Każde wypełnienie ma dostępną listę standardowych wzorców, oto przykładowy kod generujący podgląd takich deseni:

Listing 15
  1. QBrush brush(Qt::black); // obiekt klasy QBrush z ustawionym kolorem czarnym
  2. QRect rect;
  3. rect.setCoords(10, 10, 110, 50);
  4. // nazwy styli deseni
  5. QStringList styleNames = {
  6. "Qt::NoBrush",
  7. "Qt::SolidPattern",
  8. "Qt::Dense1Pattern",
  9. "Qt::Dense2Pattern",
  10. "Qt::Dense3Pattern",
  11. "Qt::Dense4Pattern",
  12. "Qt::Dense5Pattern",
  13. "Qt::Dense6Pattern",
  14. "Qt::Dense7Pattern",
  15. "Qt::HorPattern",
  16. "Qt::VerPattern",
  17. "Qt::CrossPattern",
  18. "Qt::BDiagPattern",
  19. "Qt::FDiagPattern",
  20. "Qt::DiagCrossPattern",
  21. "Qt::LinearGradientPattern"
  22. };
  23. for(int i = 0; i < Qt::LinearGradientPattern; i++){
  24. brush.setStyle(Qt::BrushStyle(i)); // ustawienie patternu
  25. painter.setBrush(brush); // ustawienie wypełnienia
  26. rect.moveTo(10, i * 40 + 20); // przemieszczenie prostokąta
  27. painter.drawRect(rect); // rysowanie prostokąta
  28. painter.drawText(120, 40 + i * 40, styleNames[i]); // rysowanie opisu stylu wypełnienia
  29. }

Na poniższym rysunku pokazany został efekt działania powyższego kodu.

Qt Creator - podstawowe style tekstur wypełnienia
Rys. 3
Qt Creator - podstawowe style tekstur wypełnienia:
  • Qt::NoBrush - brak wypełnienia;
  • Qt::SolidPattern - jednolite wypełnienie;
  • Qt::Dense1Pattern;
  • Qt::Dense2Pattern;
  • Qt::Dense3Pattern;
  • Qt::Dense4Pattern;
  • Qt::Dense5Pattern;
  • Qt::Dense6Pattern;
  • Qt::Dense7Pattern;
  • Qt::HorPattern;
  • Qt::VerPattern;
  • Qt::CrossPattern;
  • Qt::BDiagPattern;
  • Qt::FDiagPattern;
  • Qt::DiagCrossPattern;
  • Qt::LinearGradientPattern;

Obiekt QBrush ma do dyspozycji style, umożliwiające tworzenie wypełnień gradientowych. Oto spis takich styli:

  • Qt::NoBrush - brak wypełnienia;
  • Qt::ConicalGradientPattern - wypełnienie gradientem stożkowym;
  • Qt::RadialGradientPattern - wypełnienie gradientem radialnym;
  • Qt::LinearGradientPattern - wypełnienie gradientem liniowym

Istnieje też styl Qt::TexturePattern umożliwiający ustawienie jako wypełnienia swojej własnej grafiki za pomocą metody setTexture.

Operacje macierzowe

Za pomocą odpowiednich mtod klasy QPainter można:

  • skalować za pomocą metody scale;
  • przemieszczać za pomocą metody translate;
  • obracać za pomocą metody rotate;
  • obracać, skalować, przemieszczać a nawet i więcej za pomocą metody setWorldTransform i obiektu klasy QTransform.

Pierwsze trzy metody można sobie wyobrazić jak działają, natomiast ja pokażę jak można wykorzystując klasę QTransform obracać rysowany obiekt o kąt względem dowolnego punktu. Oto przykład:

Listing 16
  1. for(int i = 0; i < 360; i++){
  2. QTransform transform; // tworzę obiekt klasy QTransform
  3. transform.rotate(30 * i); // obracam go
  4. // mnożę macież przesunięcia razy macierz obrotu razy macierz powrotnego przesunięcia
  5. transform = QTransform::fromTranslate(-150, -125) * transform * QTransform::fromTranslate(150, 125); // to obraca rysowaną później elipsę o zadany kąt względem jej środka
  6. painter.setTransform(transform); // ustawienie obiektu przekształcenia liniowego
  7. painter.drawEllipse(100, 100, 100, 50); // rysowanie obróconej elipsy
  8. }
  9. painter.setTransform(QTransform()); // przywrócenie stanu początkowego

Wynik działania powyższego kodu widoczny jest na poniższym rysunku.

Qt Creator - efekt zastosowania transformacji
Rys. 4
Qt Creator - efekt zastosowania transformacji
Strony powiązane
strony powiązane
  1. doc.qt.io/qt-4.8/qpainter.html - opis klasy QPainter dostępny na stronie Qt

Komentarze