Qt利用QPainter自绘实现热感应图效果

Qt利用QPainter自绘实现热感应图效果_QPainter

 

 

https://github.com/gongjianbo/MyTestCode/tree/master/Qt/MyHeatMap  代码下载

.pro


 1 QT       += core gui
 2 
 3 greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
 4 
 5 CONFIG += c++11
 6 
 7 # The following define makes your compiler emit warnings if you use
 8 # any Qt feature that has been marked deprecated (the exact warnings
 9 # depend on your compiler). Please consult the documentation of the
10 # deprecated API in order to know how to port your code away from it.
11 DEFINES += QT_DEPRECATED_WARNINGS
12 
13 # You can also make your code fail to compile if it uses deprecated APIs.
14 # In order to do so, uncomment the following line.
15 # You can also select to disable deprecated APIs only up to a certain version of Qt.
16 #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000    # disables all the APIs deprecated before Qt 6.0.0
17 
18 SOURCES += \
19     main.cpp \
20     mainwindow.cpp
21 
22 HEADERS += \
23     mainwindow.h
24 
25 FORMS += \
26     mainwindow.ui
27 
28 # Default rules for deployment.
29 qnx: target.path = /tmp/$${TARGET}/bin
30 else: unix:!android: target.path = /opt/$${TARGET}/bin
31 !isEmpty(target.path): INSTALLS += target
View Code

 

main.cpp


 1 #include "mainwindow.h"
 2 
 3 #include <QApplication>
 4 
 5 int main(int argc, char *argv[])
 6 {
 7     QApplication a(argc, argv);
 8     MainWindow w;
 9     w.show();
10     return a.exec();
11 }
View Code

 

mainwindow.h


 1 #ifndef MAINWINDOW_H
 2 #define MAINWINDOW_H
 3 
 4 #include <QMainWindow>
 5 
 6 #include <QPainter>
 7 #include <QPaintEvent>
 8 #include <QMouseEvent>
 9 
10 #include <QLinearGradient>
11 #include <QRadialGradient>
12 
13 QT_BEGIN_NAMESPACE
14 namespace Ui { class MainWindow; }
15 QT_END_NAMESPACE
16 
17 //参照JS版:https://blog.csdn.net/HiddlestonCloud/article/details/83743449
18 //参照Qt版:https://blog.csdn.net/pbe_sedm/article/details/8982357
19 class MainWindow : public QMainWindow
20 {
21     Q_OBJECT
22     struct Point
23     {
24         int posX;
25         int posY;
26         int radius;
27         int count;
28     };
29 public:
30     MainWindow(QWidget *parent = nullptr);
31     ~MainWindow();
32 
33 protected:
34     void paintEvent(QPaintEvent *event) override;
35     void mousePressEvent(QMouseEvent *event) override;
36 
37     //添加点,返回该点权重,如果是一次性生成的点可以单独优化
38     int appendPoint(const Point &pt);
39     //最大权重改变之后重新绘制dataimg
40     void drawDataImg();
41     //叠加绘制dataimg
42     void drawDataPoint(const Point &pt);
43     //根据dataimg绘制热力图
44     void drawHeatImg();
45 
46     //清空
47     void clear();
48 
49 private:
50     Ui::MainWindow *ui;
51 
52     //整体的透明度[0-255]
53     static constexpr int HeatAlpha=200;
54     //固定宽高的演示
55     static constexpr int ImgWidth=1000;
56     static constexpr int ImgHeight=700;
57     //最大权重
58     int _maxCount=1;
59     //权重统计表(把权重单独拿出来,就可以不用遍历数据点来计算了)
60     //加()初始化为0
61     int *_countTable=new int[ImgWidth*ImgHeight]();
62 
63     //数据点
64     QList<Point> _posList;
65     //绘制透明度
66     QImage _dataImg;
67     //最终热力图
68     QImage _heatImg;
69     //颜色表,透明度为0
70     QRgb _colorList[256];
71 };
72 #endif // MAINWINDOW_H
View Code

 

mainwindow.cpp


  1 #include "mainwindow.h"
  2 #include "ui_mainwindow.h"
  3 
  4 #include <QDebug>
  5 
  6 MainWindow::MainWindow(QWidget *parent)
  7     : QMainWindow(parent)
  8     , ui(new Ui::MainWindow)
  9 {
 10     ui->setupUi(this);
 11 
 12     //这是固定宽高的演示
 13     setFixedSize(ImgWidth,ImgHeight);
 14 
 15     //data用alpha叠加
 16     _dataImg=QImage(ImgWidth,ImgHeight,QImage::Format_Alpha8);
 17     _dataImg.fill(Qt::transparent);
 18     //热力图通过alpha值查表
 19     _heatImg=QImage(ImgWidth,ImgHeight,QImage::Format_ARGB32);
 20     _heatImg.fill(Qt::transparent);
 21 
 22     //根据线性渐变色条得到颜色表
 23     QLinearGradient linear=QLinearGradient(QPoint(0,0),QPoint(255,0));
 24     linear.setColorAt(0, Qt::blue);
 25     linear.setColorAt(0.4, Qt::blue);
 26     linear.setColorAt(0.5, Qt::cyan);
 27     linear.setColorAt(0.6, Qt::green);
 28     linear.setColorAt(0.8, Qt::yellow);
 29     linear.setColorAt(0.95, Qt::red);
 30 
 31     //把渐变色绘制到Img方便取颜色
 32     QImage img(256,1,QImage::Format_ARGB32);
 33     QPainter painter(&img);
 34     painter.fillRect(img.rect(),linear);
 35 
 36     //HeatAlpha为热力图整体透明度
 37     quint32 alpha=0;
 38     for(quint32 i=0;i<256;i++){
 39         //根据热力图透明度来计算颜色表的透明度
 40         alpha=HeatAlpha/255.0*i;
 41         //typedef unsigned int QRgb: format #AARRGGBB
 42         //颜色+透明度
 43         _colorList[i]=img.pixel(i,0)&0x00FFFFFF|(alpha<<24);
 44     }
 45 
 46     //清空图像
 47     connect(ui->btnClear,&QPushButton::clicked,this,[=](){
 48         this->clear();
 49     });
 50     //保存图像
 51     connect(ui->btnSave,&QPushButton::clicked,this,[=](){
 52         _dataImg.save("save.png","PNG");
 53     });
 54 }
 55 
 56 MainWindow::~MainWindow()
 57 {
 58     delete []_countTable;
 59     delete ui;
 60 }
 61 
 62 void MainWindow::paintEvent(QPaintEvent *event)
 63 {
 64     QPainter p(this);
 65 
 66     //绘制一个黑色网格,便于查看渐变色
 67     p.setPen(QPen(Qt::black,2));
 68     for(int i=0;i<width();i+=50){
 69         p.drawLine(i,0,i,height());
 70     }
 71     for(int i=0;i<height();i+=50){
 72         p.drawLine(0,i,width(),i);
 73     }
 74     //绘制热力图
 75     p.drawImage(0,0,_heatImg);
 76     QMainWindow::paintEvent(event);
 77 }
 78 
 79 void MainWindow::mousePressEvent(QMouseEvent *event)
 80 {
 81     event->accept();
 82     const QPoint pos=event->pos();
 83     const int radius=ui->spinBox->value();
 84 
 85     //次数默认为1
 86     const Point pt{pos.x(),pos.y(),radius,1};
 87     const int pos_count=appendPoint(pt);
 88     //目前是根据最大次数来计算的,或许也可以根据次数来分段
 89     //为什么不直接if(pos_count>_maxCount)才重新绘制?
 90     //而是有两个点叠加if(pos_count>1)就重新绘制?
 91     //因为单纯的叠加目前还没有带入权重来计算,如果最大权重更大,那么这个叠加的点颜色就走样了
 92     if(pos_count>1){
 93         if(pos_count>_maxCount)
 94             _maxCount=pos_count;
 95         drawDataImg();
 96     }else{
 97         drawDataPoint(pt);
 98     }
 99     drawHeatImg();
100     update();
101 }
102 
103 int MainWindow::appendPoint(const Point &pt)
104 {
105     //无效的数据
106     if(pt.posX<0||pt.posY<0||pt.radius<0||
107             pt.posX>=ImgWidth||pt.posY>=ImgHeight)
108         return 0;
109     //根据权重表获知是否已有该点
110     if(_countTable[pt.posX+pt.posY*ImgWidth]>0){
111         for(Point &the_pos:_posList)
112         {
113             if(the_pos.posX==pt.posX&&the_pos.posY==pt.posY){
114                 //对已有点叠加权重值
115                 the_pos.count+=pt.count;
116                 break;
117             }
118         }
119     }else{
120         _posList.push_back(pt);
121     }
122     _countTable[pt.posX+pt.posY*ImgWidth]+=pt.count;
123     return _countTable[pt.posX+pt.posY*ImgWidth];
124 }
125 
126 void MainWindow::drawDataImg()
127 {
128     //重新绘制先清空
129     _dataImg.fill(Qt::transparent);
130     QPainter painter(&_dataImg);
131     painter.setPen(Qt::transparent);
132     //绘制点的部分可以调用drawpoint
133     const double max_count=_maxCount;
134     for(int i=0;i<_posList.count();i++)
135     {
136         const Point &pt=_posList.at(i);
137         //以最大次数来计算该点的权重
138         const uchar alpha=uchar(_countTable[pt.posX+pt.posY*ImgWidth]/max_count*255);
139         QRadialGradient gradient(pt.posX,pt.posY,pt.radius);
140         gradient.setColorAt(0,QColor(0,0,0,alpha));
141         gradient.setColorAt(1,QColor(0,0,0,0));
142         painter.setBrush(gradient);
143         painter.drawEllipse(QPointF(pt.posX,pt.posY),pt.radius,pt.radius);
144     }
145 }
146 
147 void MainWindow::drawDataPoint(const Point &pt)
148 {
149     QPainter painter(&_dataImg);
150     painter.setPen(Qt::transparent);
151 
152     //以最大次数来计算该点的权重
153     const uchar alpha=uchar(_countTable[pt.posX+pt.posY*ImgWidth]/(double)_maxCount*255);
154 
155     QRadialGradient gradient(pt.posX,pt.posY,pt.radius);
156     gradient.setColorAt(0,QColor(0,0,0,alpha));
157     gradient.setColorAt(1,QColor(0,0,0,0));
158     painter.setBrush(gradient);
159     painter.drawEllipse(QPointF(pt.posX,pt.posY),pt.radius,pt.radius);
160 }
161 
162 void MainWindow::drawHeatImg()
163 {
164     //把alpha值转为颜色值
165     for(int row=0;row<_dataImg.height();row++)
166     {
167         //dataimg QImage::Format_Alpha8,一个点1个字节
168         const uchar *line_data=_dataImg.scanLine(row);
169         //heatimg QImage::Format_ARGB32,一个点4个字节
170         QRgb *line_heat=reinterpret_cast<QRgb*>(_heatImg.scanLine(row));
171         for(int col=0;col<_dataImg.width();col++)
172         {
173             //根据alpha透明度从颜色表取颜色
174             line_heat[col]=_colorList[line_data[col]];
175         }
176     }
177 }
178 
179 void MainWindow::clear()
180 {
181     _dataImg.fill(Qt::transparent);
182     _heatImg.fill(Qt::transparent);
183     _posList.clear();
184     _maxCount=1;
185     memset(_countTable,0,ImgWidth*ImgHeight*sizeof(int));
186     update();
187 }
View Code

 

mainwindow.ui


  1 <?xml version="1.0" encoding="UTF-8"?>
  2 <ui version="4.0">
  3  <class>MainWindow</class>
  4  <widget class="QMainWindow" name="MainWindow">
  5   <property name="geometry">
  6    <rect>
  7     <x>0</x>
  8     <y>0</y>
  9     <width>667</width>
 10     <height>452</height>
 11    </rect>
 12   </property>
 13   <property name="windowTitle">
 14    <string>MainWindow</string>
 15   </property>
 16   <widget class="QWidget" name="centralwidget">
 17    <layout class="QVBoxLayout" name="verticalLayout">
 18     <property name="leftMargin">
 19      <number>0</number>
 20     </property>
 21     <property name="topMargin">
 22      <number>0</number>
 23     </property>
 24     <property name="rightMargin">
 25      <number>0</number>
 26     </property>
 27     <property name="bottomMargin">
 28      <number>0</number>
 29     </property>
 30     <item>
 31      <layout class="QHBoxLayout" name="horizontalLayout">
 32       <item>
 33        <widget class="QLabel" name="label">
 34         <property name="text">
 35          <string>Radius</string>
 36         </property>
 37        </widget>
 38       </item>
 39       <item>
 40        <widget class="QSpinBox" name="spinBox">
 41         <property name="minimum">
 42          <number>10</number>
 43         </property>
 44         <property name="maximum">
 45          <number>200</number>
 46         </property>
 47         <property name="value">
 48          <number>50</number>
 49         </property>
 50        </widget>
 51       </item>
 52       <item>
 53        <spacer name="horizontalSpacer">
 54         <property name="orientation">
 55          <enum>Qt::Horizontal</enum>
 56         </property>
 57         <property name="sizeHint" stdset="0">
 58          <size>
 59           <width>40</width>
 60           <height>20</height>
 61          </size>
 62         </property>
 63        </spacer>
 64       </item>
 65       <item>
 66        <widget class="QPushButton" name="btnClear">
 67         <property name="text">
 68          <string>clear</string>
 69         </property>
 70        </widget>
 71       </item>
 72       <item>
 73        <widget class="QPushButton" name="btnSave">
 74         <property name="text">
 75          <string>save</string>
 76         </property>
 77        </widget>
 78       </item>
 79      </layout>
 80     </item>
 81     <item>
 82      <spacer name="verticalSpacer">
 83       <property name="orientation">
 84        <enum>Qt::Vertical</enum>
 85       </property>
 86       <property name="sizeHint" stdset="0">
 87        <size>
 88         <width>20</width>
 89         <height>373</height>
 90        </size>
 91       </property>
 92      </spacer>
 93     </item>
 94    </layout>
 95   </widget>
 96   <widget class="QMenuBar" name="menubar">
 97    <property name="geometry">
 98     <rect>
 99      <x>0</x>
100      <y>0</y>
101      <width>667</width>
102      <height>23</height>
103     </rect>
104    </property>
105   </widget>
106   <widget class="QStatusBar" name="statusbar"/>
107  </widget>
108  <resources/>
109  <connections/>
110 </ui>
View Code

 

作者:疯狂Delphi