Qt利用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
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 }
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
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 }
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>