工作中在用Qt写界面程序时需要完成一项功能:客户端和服务端连接成功后需要实时显示从服务端发送过来的图片,并可以用鼠标滚轮进行缩放以及拖拽。由于之前学习过些许OpenGL关于纹理贴图的技术,且Qt已集成OpenGL模块,因此打算用该技术完成。OpenGL显示图片使用GPU渲染,如果程序需要做到毫秒级的刷新频率,用该方法可以很大程度上缓解CPU的压力,图片的动态显示也更流畅。
下面我用一个demo程序简要记录下自己的使用方法,我会把代码都贴上来。我创建了一个简单的对话框应用程序,对话框放了一个widget控件,然后自己写了一个GL_Image类,继承自QGLWidget,实现了我需要完成的鼠标控制功能,同时为防止图片显示变形做了必要的等比例缩放,GL_Image类的对象将图片绘制在widget控件上。
也可以在将GL_Image类改成继承QOpenGLWidget的实现,修改很简单,见博客下方评论。
1、在pro文件加入Qt对OpenGL模块的支持,在QT+=的最后加上opengl
QT += core gui opengl
2、GL_Image类
gl_image.h
#ifndef GL_IMAGE_H
#define GL_IMAGE_H
#include
#include
#include
class GL_Image : public QGLWidget
{
Q_OBJECT
public:
enum
{
Left_Bottom_X,
Left_Bottom_Y,
Right_Bottom_X,
Right_Bottom_Y,
Right_Top_X,
Right_Top_Y,
Left_Top_X,
Left_Top_Y,
Pos_Max
};
GL_Image(QWidget* parent = nullptr);
// 设置实时显示的数据源
void setImageData(uchar* imageSrc, uint width, uint height);
protected:
// 重写QGLWidget类的接口
void initializeGL();
void paintGL();
void resizeGL(int w, int h);
// 鼠标事件
void wheelEvent(QWheelEvent* e);
void mouseMoveEvent(QMouseEvent* e);
void mousePressEvent(QMouseEvent* e);
void mouseReleaseEvent(QMouseEvent* e);
void mouseDoubleClickEvent(QMouseEvent* e);
private:
uchar* imageData_; //纹理显示的数据源
QSize imageSize_; //图片尺寸
QSize adaptImageSize_; //适配尺寸
QSize Ortho2DSize_; //窗口尺寸
GLuint textureId_; //纹理对象ID
int vertexPos_[Pos_Max]; //窗口坐标
float texturePos_[Pos_Max]; //纹理坐标
bool dragFlag_; //鼠标拖拽状态
QPoint dragPos_; //鼠标拖拽位置
float scaleVal_; //缩放倍率
};
#endif // GL_IMAGE_H
gl_image.cpp
#include "gl_image.h"
GL_Image::GL_Image(QWidget* parent):
QGLWidget(parent)
{
imageData_ = nullptr;
dragFlag_ = false;
scaleVal_ = 1.0;
}
// 设置待显示的数据源和尺寸
void GL_Image::setImageData(uchar* imageSrc, uint width, uint height)
{
imageData_ = imageSrc;
imageSize_.setWidth(width);
imageSize_.setHeight(height);
}
void GL_Image::initializeGL()
{
// 生成一个纹理ID
glGenTextures(1, &textureId_);
// 绑定该纹理ID到二维纹理上
glBindTexture(GL_TEXTURE_2D, textureId_);
// 用线性插值实现图像缩放
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
}
// 窗口绘制函数
void GL_Image::paintGL()
{
static bool initTextureFlag = false;
// 设置背景颜色
glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
if(imageData_ == nullptr){
return;
}
glBindTexture(GL_TEXTURE_2D, textureId_);
if(!initTextureFlag)
{
// 生成纹理
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, imageSize_.width(), imageSize_.height(), 0, GL_RGBA, GL_UNSIGNED_BYTE, imageData_);
// 初始化顶点坐标(居中显示)
int x_offset = 0;
int y_offset = 0;
if(imageSize_.width()delta() > 0)
{
scaleVal_ += 0.1;
scaleVal_ = scaleVal_>3? 3:scaleVal_;
}
else
{
scaleVal_ -= 0.1;
scaleVal_ = scaleVal_<0.1? 0.1:scaleVal_;
}
uint16_t showImgWidth = adaptImageSize_.width() * scaleVal_;
uint16_t showImgHeight = adaptImageSize_.height() * scaleVal_;
int xoffset = (Ortho2DSize_.width() - showImgWidth)/2;
int yoffset = (Ortho2DSize_.height() - showImgHeight)/2;
vertexPos_[Left_Bottom_X] = xoffset;
vertexPos_[Left_Bottom_Y] = yoffset;
vertexPos_[Right_Bottom_X] = xoffset + showImgWidth;
vertexPos_[Right_Bottom_Y] = yoffset;
vertexPos_[Right_Top_X] = xoffset + showImgWidth;
vertexPos_[Right_Top_Y] = yoffset + showImgHeight;
vertexPos_[Left_Top_X] = xoffset;
vertexPos_[Left_Top_Y] = yoffset + showImgHeight;
paintGL();
}
// 实现鼠标拖拽图片,鼠标在拖拽过程中会反复调用此函数,因此一个连续的拖拽过程可以
// 分解为多次移动的过程,每次移动都是在上一个位置的基础上进行一次位置调节
void GL_Image::mouseMoveEvent(QMouseEvent* e)
{
if (dragFlag_)
{
int scaledMoveX = e->x()-dragPos_.x();
int scaledMoveY = e->y()-dragPos_.y();
vertexPos_[Left_Bottom_X] += scaledMoveX;
vertexPos_[Left_Bottom_Y] += scaledMoveY;
vertexPos_[Left_Top_X] += scaledMoveX;
vertexPos_[Left_Top_Y] += scaledMoveY;
vertexPos_[Right_Top_X] += scaledMoveX;
vertexPos_[Right_Top_Y] += scaledMoveY;
vertexPos_[Right_Bottom_X] += scaledMoveX;
vertexPos_[Right_Bottom_Y] += scaledMoveY;
dragPos_.setX(e->x());
dragPos_.setY(e->y());
paintGL();
}
}
void GL_Image::mousePressEvent(QMouseEvent* e)
{
if(scaleVal_ > 0)
{
dragFlag_ = true;
dragPos_.setX(e->x());
dragPos_.setY(e->y());
}
}
void GL_Image::mouseReleaseEvent(QMouseEvent* e)
{
dragFlag_ = false;
}
// 双击实现原比例显示,缩放倍率设置为1.0
void GL_Image::mouseDoubleClickEvent(QMouseEvent* e)
{
scaleVal_ = 1.0;
uint16_t showImgWidth = adaptImageSize_.width() * scaleVal_;
uint16_t showImgHeight = adaptImageSize_.height() * scaleVal_;
int xoffset = (Ortho2DSize_.width() - showImgWidth)/2;
int yoffset = (Ortho2DSize_.height() - showImgHeight)/2;
vertexPos_[Left_Bottom_X] = xoffset;
vertexPos_[Left_Bottom_Y] = yoffset;
vertexPos_[Right_Bottom_X] = xoffset + showImgWidth;
vertexPos_[Right_Bottom_Y] = yoffset;
vertexPos_[Right_Top_X] = xoffset + showImgWidth;
vertexPos_[Right_Top_Y] = yoffset + showImgHeight;
vertexPos_[Left_Top_X] = xoffset;
vertexPos_[Left_Top_Y] = yoffset + showImgHeight;
paintGL();
}
对话框头文件dialog.h
#ifndef DIALOG_H
#define DIALOG_H
#include
#include
#include "gl_image.h"
namespace Ui {
class Dialog;
}
class Dialog : public QDialog
{
Q_OBJECT
public:
explicit Dialog(QWidget *parent = nullptr);
~Dialog();
private slots:
void slotTimeOut();
private:
Ui::Dialog *ui;
GL_Image* glImage;
QTimer timer;
};
#endif // DIALOG_H
对话框实现文件dialog.cpp
#include "dialog.h"
#include "ui_dialog.h"
Dialog::Dialog(QWidget *parent) :
QDialog(parent),
ui(new Ui::Dialog)
{
ui->setupUi(this);
// 将widget控件作为绘制窗口
glImage = new GL_Image(ui->widget);
glImage->setFixedSize(ui->widget->size());
connect(&timer, SIGNAL(timeout()), this, SLOT(slotTimeOut()));
timer.setTimerType(Qt::PreciseTimer);
timer.start(100);
}
Dialog::~Dialog()
{
delete ui;
}
// 主界面开启定时器,在界面循环显示4个方向的图片
void Dialog::slotTimeOut()
{
static uint i = 0;
char imageName[100];
// 需要修改为自己的图片路径
sprintf(imageName, "/home/gk/program/Qt/QtOpenGLWidget/images/lena%d.jpg", i++%4);
QImage image(imageName);
QImage rgba = image.rgbSwapped(); //qimage加载的颜色通道顺序和opengl显示的颜色通道顺序不一致,调换R通道和B通道
glImage->setImageData(rgba.bits(), rgba.width(), rgba.height());
glImage->repaint(); //窗口重绘,repaint会调用paintEvent函数,paintEvent会调用paintGL函数实现重绘
}
入口文件main.cpp
#include
#include "dialog.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Dialog w;
w.show();
return a.exec();
}
gif效果图简单演示了实时显示和鼠标滚轮缩放、拖拽功能
至此demo代码都贴完了,另外demo工程也打包上传到了这里。