Qt 5 C++ – Painting

1/5 - (3 votes)

Trong phần này chúng ta sẽ học cách vẽ trong Qt. Lớp QPainter với phương thức paintEvent() là lớp chính để vẽ tất cả mọi thứ.

Đường thẳng

Để bắt đầu chúng ta sẽ vẽ vài đường thẳng đơn giản.

#pragma once

#include <QWidget>

class Lines : public QWidget {

  public:
    Lines(QWidget *parent = 0);

  protected:
    void paintEvent(QPaintEvent *event);
    void drawLines(QPainter *qp);
};
#include <QPainter>
#include "lines.h"

Lines::Lines(QWidget *parent)
    : QWidget(parent)
{ }

void Lines::paintEvent(QPaintEvent *e) {
  QPainter qp(this);
  drawLines(&qp);
}

void Lines::drawLines(QPainter *qp) {
  
  QPen pen(Qt::black, 2, Qt::SolidLine);  
  qp->setPen(pen);
  qp->drawLine(20, 40, 250, 40);

  pen.setStyle(Qt::DashLine);
  qp->setPen(pen);
  qp->drawLine(20, 80, 250, 80);

  pen.setStyle(Qt::DashDotLine);
  qp->setPen(pen);
  qp->drawLine(20, 120, 250, 120);

  pen.setStyle(Qt::DotLine);
  qp->setPen(pen);
  qp->drawLine(20, 160, 250, 160);

  pen.setStyle(Qt::DashDotDotLine);
  qp->setPen(pen);
  qp->drawLine(20, 200, 250, 200);

  QVector<qreal> dashes;
  qreal space = 4;

  dashes << 1 << space << 5 << space; pen.setStyle(Qt::CustomDashLine); pen.setDashPattern(dashes); qp->setPen(pen);
  qp->drawLine(20, 240, 250, 240);
}

Chúng ta vẽ 6 đường thẳng lên màn hình, mỗi đường thẳng vẽ một kiểu riêng.

void Lines::paintEvent(QPaintEvent *e) {
    
  Q_UNUSED(e);
  
  QPainter qp(this);
  drawLines(&qp);
}

Chúng ta override phương thức paintEvent() của lớp QWidget, phương thức này tự động được gọi khi các trạng thái của widget thay đổi, chẳng hạn như resize kích thước cửa sổ, ẩn/hiện widget… Bên trong phương thức chúng ta tạo đối tượng QPainter để vẽ.

QPen pen(Qt::black, 2, Qt::SolidLine);
qp->setPen(pen);

Chúng ta tạo đối tượng QPen. Tham số đầu tiên là màu vẽ, tham số thứ 2 là độ lớn của đường thẳng, tham số thứ 3 là loại đường thẳng, ở đây là Qt::SolidLine tức là đường thẳng bình thường. Bạn có thể tham khảo một số loại đường thẳng khác ở đây. Sau khi có QPen thì phải gắn nó vào với đối tượng QPainter bằng phương thức setPen(). Có thể hiểu QPainter là thợ vẽ, QPen là cây bút vẽ, thợ vẽ khi vẽ sẽ chọn loại bút để vẽ theo ý mình.

qp->drawLine(20, 40, 250, 40);

Phương thức drawLine()  vẽ một đường thẳng. Phương thức này nhận 4 tham số là tọa độ điểm bắt đầu và tọa độ điểm kết thúc của đường thẳng.

pen.setStyle(Qt::DashLine);

Phương thức setStyle() sẽ thay đổi loại đường thẳng mà chúng ta muốn.

#include <QApplication>
#include "lines.h"

int main(int argc, char *argv[]) {
    
  QApplication app(argc, argv);  
    
  Lines window;
  
  window.resize(280, 270);
  window.setWindowTitle("Lines");
  window.show();

  return app.exec();
}
Capturea

Màu sắc

Màu sắc trong máy tính được biểu diễn bởi 3 giá trị là đỏ (Red), vàng (Yellow) và xanh nước biển (Blue) – RGB. Mỗi giá trị này nhận giá trị từ 0→255. Ví dụ dưới đây sẽ vẽ 9 hình chữ nhật với 9 màu khác nhau.

#pragma once 

#include <QWidget>

class Colours : public QWidget {
    
  public:
    Colours(QWidget *parent = 0);

  protected:
    void paintEvent(QPaintEvent *e);
    
  private:
    void doPainting();  
};
#include <QPainter>
#include "colours.h"

Colours::Colours(QWidget *parent)
    : QWidget(parent)
{ }

void Colours::paintEvent(QPaintEvent *e) {
    
  Q_UNUSED(e);  
  
  doPainting();
}

void Colours::doPainting() {
    
  QPainter painter(this);
  painter.setPen(QColor("#d4d4d4"));

  painter.setBrush(QBrush("#c56c00"));
  painter.drawRect(10, 15, 90, 60);

  painter.setBrush(QBrush("#1ac500"));
  painter.drawRect(130, 15, 90, 60);

  painter.setBrush(QBrush("#539e47"));
  painter.drawRect(250, 15, 90, 60);
  
  painter.setBrush(QBrush("#004fc5"));
  painter.drawRect(10, 105, 90, 60);

  painter.setBrush(QBrush("#c50024"));
  painter.drawRect(130, 105, 90, 60);

  painter.setBrush(QBrush("#9e4757"));
  painter.drawRect(250, 105, 90, 60);

  painter.setBrush(QBrush("#5f3b00"));
  painter.drawRect(10, 195, 90, 60);

  painter.setBrush(QBrush("#4c4c4c"));
  painter.drawRect(130, 195, 90, 60);

  painter.setBrush(QBrush("#785f36"));
  painter.drawRect(250, 195, 90, 60);
}

Chúng ta vẽ 9 hình chữ nhật có 9 màu khác nhau.

painter.setBrush(QBrush("#c56c00"));
painter.drawRect(10, 15, 90, 60);

Lớp QBrush quy định kiểu vẽ các đối tượng hình học được vẽ bởi lớp QPainter. Phương thức drawRect() của lớp QPainter vẽ một hình chữ nhật với 4 tham số là tọa độ x-y của góc trái-trên và tọa độ x-y của góc phải-dưới của hình chữ nhật.

#include <QApplication>
#include "colours.h"

int main(int argc, char *argv[]) {
    
  QApplication app(argc, argv);  
    
  Colours window;
  
  window.resize(360, 280);
  window.setWindowTitle("Colours");
  window.show();
  
  return app.exec();
}
Capture

Pattern

Ví dụ dưới đây cũng sẽ vẽ các hình chữ nhật như ví dụ ở trên, chỉ khác là chúng ta sẽ khai thác các kiểu vẽ khác nhau có trong Qt. Trong ví dụ trên thì chúng ta cho vẽ mặc định theo kiểu tô màu toàn bộ phần bên trong hình chữ nhật.

#pragma once

#include <QWidget>

class Patterns : public QWidget {
    
  public:
    Patterns(QWidget *parent = 0);

  protected:
    void paintEvent(QPaintEvent *e);

  private:
    void doPainting();
};
#include <QApplication>
#include <QPainter>
#include "patterns.h"

Patterns::Patterns(QWidget *parent)
    : QWidget(parent)
{ }

void Patterns::paintEvent(QPaintEvent *e) {
    
  Q_UNUSED(e);  
  
  doPainting();
}

void Patterns::doPainting() {
    
  QPainter painter(this);
  painter.setPen(Qt::NoPen);

  painter.setBrush(Qt::HorPattern);
  painter.drawRect(10, 15, 90, 60);

  painter.setBrush(Qt::VerPattern);
  painter.drawRect(130, 15, 90, 60);

  painter.setBrush(Qt::CrossPattern);
  painter.drawRect(250, 15, 90, 60);
  
  painter.setBrush(Qt::Dense7Pattern);
  painter.drawRect(10, 105, 90, 60);

  painter.setBrush(Qt::Dense6Pattern);
  painter.drawRect(130, 105, 90, 60);

  painter.setBrush(Qt::Dense5Pattern);
  painter.drawRect(250, 105, 90, 60);

  painter.setBrush(Qt::BDiagPattern);
  painter.drawRect(10, 195, 90, 60);

  painter.setBrush(Qt::FDiagPattern);
  painter.drawRect(130, 195, 90, 60);

  painter.setBrush(Qt::DiagCrossPattern);
  painter.drawRect(250, 195, 90, 60);
}

Chúng ta vẽ 9 hình chữ nhật với 9 kiểu vẽ khác nhau.

#include <QDesktopWidget>
#include <QApplication>
#include "patterns.h"

int main(int argc, char *argv[]) {
    
  QApplication app(argc, argv);  
    
  Patterns window;
  
  window.resize(350, 280);
  window.setWindowTitle("Patterns");
  window.show();

  return app.exec();
}
Capture

Độ trong suốt – Transparency

Trong đồ họa máy tính, độ trong suốt được định nghĩa bằng giá trị alpha, giá trị này được máy tính định nghĩa bằng cách kết hợp giá trị màu RGB tại một tọa độ với giá trị màu nền để cho ra độ trong suốt.

#pragma once

#include <QWidget>

class TransparentRectangles : public QWidget {

  public:
    TransparentRectangles(QWidget *parent = 0);

  protected:
    void paintEvent(QPaintEvent *event);
    void doPainting();
};
#include <QApplication>
#include <QPainter>
#include <QPainterPath>
#include "transparent_rectangles.h"

TransparentRectangles::TransparentRectangles(QWidget *parent)
    : QWidget(parent)
{ }

void TransparentRectangles::paintEvent(QPaintEvent *e) {
    
  Q_UNUSED(e);
  
  doPainting();
}

void TransparentRectangles::doPainting() {
    
  QPainter painter(this);
  
  for (int i=1; i<=11; i++) {
    painter.setOpacity(i*0.1);
    painter.fillRect(50*i, 20, 40, 40, Qt::darkGray);
  }    
}

Ví dụ trên vẽ 10 hình chữ nhật với độ trong suốt từ 0.0→1.0.

painter.setOpacity(i*0.1);

Phương thức setOpacity() quy định độ trong suốt, có giá trị từ 0.0 đến 1.0. Trong đó 0.0 là không nhìn thấy gì còn 1.0 là rõ hoàn toàn.

#include <QDesktopWidget>
#include <QApplication>
#include "transparent_rectangles.h"

int main(int argc, char *argv[]) {
    
  QApplication app(argc, argv);  
    
  TransparentRectangles window;

  window.resize(630, 90);  
  window.setWindowTitle("Transparent rectangles");
  window.show();

  return app.exec();
}

Capture

Shape

Thư viện của Qt có thể vẽ được rất nhiều đối tượng hình học (shape) khác nhau.

#pragma once

#include <QWidget>

class Shapes : public QWidget {
    
  public:
    Shapes(QWidget *parent = 0);

  protected:
    void paintEvent(QPaintEvent *e);

  private:
    void doPainting();
};
#include <QApplication>
#include <QPainter>
#include <QPainterPath>
#include "shapes.h"

Shapes::Shapes(QWidget *parent)
    : QWidget(parent)
{ }

void Shapes::paintEvent(QPaintEvent *e) {
    
  Q_UNUSED(e);

  doPainting();
}

void Shapes::doPainting() {
  
  QPainter painter(this);

  painter.setRenderHint(QPainter::Antialiasing);
  painter.setPen(QPen(QBrush("#888"), 1));
  painter.setBrush(QBrush(QColor("#888")));

  QPainterPath path1;

  path1.moveTo(5, 5);
  path1.cubicTo(40, 5,  50, 50,  99, 99);
  path1.cubicTo(5, 99,  50, 50,  5, 5);
  painter.drawPath(path1);  

  painter.drawPie(130, 20, 90, 60, 30*16, 120*16);
  painter.drawChord(240, 30, 90, 60, 0, 16*180);
  painter.drawRoundRect(20, 120, 80, 50);

  QPolygon polygon({QPoint(130, 140), QPoint(180, 170), QPoint(180, 140),
      QPoint(220, 110), QPoint(140, 100)});

  painter.drawPolygon(polygon);

  painter.drawRect(250, 110, 60, 60);

  QPointF baseline(20, 250);
  QFont font("Georgia", 55);
  QPainterPath path2;
  path2.addText(baseline, font, "Q");
  painter.drawPath(path2);

  painter.drawEllipse(140, 200, 60, 60);
  painter.drawEllipse(240, 200, 90, 60);
}

Ví dụ trên vẽ 9 hình khác nhau.

QPainterPath path1;

path1.moveTo(5, 5);
path1.cubicTo(40, 5,  50, 50,  99, 99);
path1.cubicTo(5, 99,  50, 50,  5, 5);
painter.drawPath(path1);

Lớp QPainterPath là lớp được dùng để vẽ các hình phức tạp. Phương thức cubicTo(x1, y1, x2, y2, endPointX, endPointY) của lớp này sẽ vẽ đường cong Bezier từ vị trí hiện tại (mặc định hiện tại là tọa độ {0,0}) đến vị trí endPoint, các điểm được sử dụng để điều khiển quy trình vẽ và {x1, y1}{x2, y2}. Các bạn có thể tham khảo wiki về thuật toán vẽ đường cong Bezier.

painter.drawPie(130, 20, 90, 60, 30*16, 120*16);
painter.drawChord(240, 30, 90, 60, 0, 16*180);
painter.drawRoundRect(20, 120, 80, 50);

Phương thức drawPie(x, y, width, height, startAngle, spanAngle) và  drawChord(x, y, width, height, startAngle, spanAngle) vẽ hình tròn hoặc một phần của hình tròn bên trong một hình chữ nhật có vị trí {x, y} với chiều dài-rộng là {width, height}. Tham số startAngle spanAngle nhận giá trị là (1/16 * số đo một góc), startAngle là góc xoay, còn spanAngle là góc giữa hai dây cung, ví dụ nếu spanAngle là 16 * 360 thì khi đó ta được một hình tròn nguyên vẹn, 16 * 180 là nửa hình tròn, 16 * 120 sẽ ra hình quạt. Các bạn có thể thay đổi 2 tham số này để thấy sự thay đổi.

QPolygon polygon({QPoint(130, 140), QPoint(180, 170), QPoint(180, 140),
    QPoint(220, 110), QPoint(140, 100)});

painter.drawPolygon(polygon);

Phương thức drawPolygon() được dùng để vẽ một đa giác bất kỳ dùng tập hợp điểm trong lớp QPolygon. Ở ví dụ trên chúng ta có 5 điểm.

QPointF baseline(20, 250);
QFont font("Georgia", 55);
QPainterPath path2;
path2.addText(baseline, font, "Q");
painter.drawPath(path2);

Qt còn cho phép chúng ta vẽ hình dựa theo ký tự.

painter.drawEllipse(140, 200, 60, 60);
painter.drawEllipse(240, 200, 90, 60);

Phương thức drawEllipse() vẽ hình elip và có thể cả hình tròn nếu muốn. Tham số đầu vào là tọa độ {x, y} và chiều dài-rộng của một hình chữ nhật, Qt sẽ vẽ elip bên trong hình chữ nhật đó.

#include <QDesktopWidget>
#include <QApplication>
#include "shapes.h"

int main(int argc, char *argv[]) {
    
  QApplication app(argc, argv);  
    
  Shapes window;

  window.resize(350, 280);  
  window.setWindowTitle("Shapes");
  window.show();

  return app.exec();
}

Capture

Gradient

Gradient là dải màu từ màu này đến màu khác. Có thể ứng dụng để làm mô phỏng bóng tối và ánh sáng. Trong ví dụ này chúng ta dùng linear gradient.

#pragma once

#include <QWidget>

class LinearGradients : public QWidget {

  public:
    LinearGradients(QWidget *parent = 0);

  protected:
    void paintEvent(QPaintEvent *e);
    
  private:
    void doPainting();  
};
#include <QApplication>
#include <QPainter>
#include "linear_gradients.h"

LinearGradients::LinearGradients(QWidget *parent)
    : QWidget(parent)
{ }

void LinearGradients::paintEvent(QPaintEvent *e) {
    
  Q_UNUSED(e);
  
  doPainting();
}  

void LinearGradients::doPainting() {
         
  QPainter painter(this);
  
  QLinearGradient grad1(0, 20, 0, 110);

  grad1.setColorAt(0.1, Qt::black);
  grad1.setColorAt(0.5, Qt::yellow);
  grad1.setColorAt(0.9, Qt::black);

  painter.fillRect(20, 20, 300, 90, grad1);

  QLinearGradient grad2(0, 55, 250, 0);

  grad2.setColorAt(0.2, Qt::black);
  grad2.setColorAt(0.5, Qt::red);
  grad2.setColorAt(0.8, Qt::black);

  painter.fillRect(20, 140, 300, 100, grad2);
}

Chúng ta vẽ 2 hình chữ nhật và tô màu gradient cho chúng.

QLinearGradient grad1(0, 20, 0, 110);

Lớp QLinearGradient tạo ra dải màu giữa 2 điểm.

grad1.setColorAt(0.1, Qt::black);
grad1.setColorAt(0.5, Qt::yellow);
grad1.setColorAt(0.9, Qt::black);

Dải màu trong gradient được tô giữa các điểm dừng. Bạn có thể điều chỉnh các tham số để thấy sự thay đổi.

painter.fillRect(20, 20, 300, 90, grad1);

Tô màu hình chữ nhật với gradient.

#include <QApplication>
#include "linear_gradients.h"

int main(int argc, char *argv[]) {
    
  QApplication app(argc, argv);  
    
  LinearGradients window;

  window.resize(350, 260);  
  window.setWindowTitle("Linear gradients");
  window.show();

  return app.exec();
}
Capture

Radial gradient

Radial gradient là dải màu giữa hai hình tròn.

#pragma once

#include <QWidget>

class RadialGradient : public QWidget {

  public:
    RadialGradient(QWidget *parent = 0);

  protected:
    void paintEvent(QPaintEvent *e);
    
  private:
    void doPainting();  
};
#include <QApplication>
#include <QPainter>
#include "radial_gradient.h"

RadialGradient::RadialGradient(QWidget *parent)
    : QWidget(parent)
{ }

void RadialGradient::paintEvent(QPaintEvent *e) {
    
  Q_UNUSED(e);
  
  doPainting();
}

void RadialGradient::doPainting() {
  
  QPainter painter(this);
  
  int h = height();
  int w = width();

  QRadialGradient grad1(w/2, h/2, 80);

  grad1.setColorAt(0, QColor("#032E91"));
  grad1.setColorAt(0.3, Qt::white);
  grad1.setColorAt(1, QColor("#032E91"));

  painter.fillRect(0, 0, w, h, grad1);
}

Ví dụ trên tạo dải màu quanh hình tròn, dải màu này sẽ xuất phát từ tâm hình tròn.

#include <QApplication>
#include "radial_gradient.h"

int main(int argc, char *argv[]) {
    
  QApplication app(argc, argv);  
    
  RadialGradient window;

  window.resize(300, 250);  
  window.setWindowTitle("Radial gradient");
  window.show();

  return app.exec();
}
Capture

Animation

Trong ví dụ cuối cùng chúng ta sẽ làm hiệu ứng chuyển động. Chúng ta sẽ cho một đoạn chữ xuất phát từ tâm màn hình với kích thước lớn dần dần, màu chữ cũng sẽ từ từ nhạt đi trong một khoảng thời gian.

#pragma once

#include <QWidget>

class Puff : public QWidget {

  public:
    Puff(QWidget *parent = 0);

  protected:
    void paintEvent(QPaintEvent *event);
    void timerEvent(QTimerEvent *event);

  private:
    int x;
    qreal opacity;
    int timerId;
    
    void doPainting();
};

Chúng ta override hai phương thức paintEvent()timerEvent().

#include <QPainter>
#include <QTimer>
#include <QTextStream>
#include "puff.h"

Puff::Puff(QWidget *parent)
    : QWidget(parent) {
        
  x = 1;
  opacity = 1.0;
  timerId = startTimer(15);
}

void Puff::paintEvent(QPaintEvent *e) {
    
  Q_UNUSED(e);  
  
  doPainting();
}

void Puff::doPainting() {
  
  QPainter painter(this);
  QTextStream out(stdout);

  QString text = "Pho Code";

  painter.setPen(QPen(QBrush("#575555"), 1));

  QFont font("Courier", x, QFont::DemiBold);
  QFontMetrics fm(font);
  int textWidth = fm.width(text);

  painter.setFont(font);

  if (x < 10) { opacity -= 0.01; painter.setOpacity(opacity); } if (opacity >= 0) {
    killTimer(timerId);
    out << "timer stopped" << endl;
  }

  int h = height();
  int w = width();

  painter.translate(QPoint(w/2, h/2));
  painter.drawText(-textWidth/2, 0, text);
}

void Puff::timerEvent(QTimerEvent *e) {
    
  Q_UNUSED(e);
  
  x += 1;
  repaint();
}
Puff::Puff(QWidget *parent)
    : QWidget(parent) {
        
  x = 1;
  opacity = 1.0;
  timerId = startTimer(15);
}

Chúng ta cho chạy timer với độ trễ là 15ms.

void Puff::timerEvent(QTimerEvent *e) {
    
  Q_UNUSED(e);
  
  x += 1;
  repaint();
}

Cứ sau 15ms, chúng ta sẽ cho tăng kích thước chữ và gọi phương thức repaint().

if (x > 10) {
  opacity -= 0.01;
  painter.setOpacity(opacity);
}

Nếu kích thước chữ lớn hơn 10, chúng ta bắt đầu làm mờ chữ.

if (opacity <= 0) {
  killTimer(timerId);
  out << "timer stopped" << endl;
}

Khi độ trong suốt của chữ đã biến mất hoàn toàn thì chúng ta dừng timer.

#include <QApplication>
#include "puff.h"

int main(int argc, char *argv[]) {
    
  QApplication app(argc, argv); 

  Puff window;

  window.resize(350, 280);
  window.setWindowTitle("Puff");
  window.show();

  return app.exec();
}

Untitled

0 0 votes
Article Rating
Subscribe
Thông báo cho tôi qua email khi
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

2 Comments
Inline Feedbacks
View all comments
Vũ Viết Tiến
Vũ Viết Tiến
7 năm trước

các anh có code vẽ đường cong bezier trên dev C++ không cho em xin với