Skip to main content
标签ad报错:该广告ID(9)不存在。
  主页 > Qt入门

一个Qt/C++ Demo,用于实现一个用于显示3D频谱图的控件效果

2023-04-23 浏览:
标签ad报错:该广告ID(7)不存在。
#pragma once

#include <QWidget>
#include <QHBoxLayout>
#include <QLabel>
#include <QPixmap>
#include <QPainter>
#include <QTimer>
#include <fftw3.h>
#include <QOpenGLWidget>
#include <QOpenGLFunctions>

class SpectrumWidget : public QOpenGLWidget, protected QOpenGLFunctions {
   Q_OBJECT
public:
   explicit SpectrumWidget(QWidget* parent = nullptr) :
       QOpenGLWidget(parent),
       m_data(1000, 0),
       m_spectrumData(m_data.size()),
       m_timer(new QTimer(this)),
       m_xRot(-90.0f),
       m_yRot(0.0f),
       m_zRot(0.0f)
   {

   }

   ~SpectrumWidget() override {
       delete m_timer;
       fftw_destroy_plan(m_plan);
   }

   void start() {
       m_timer->start(50);
   }

   void stop() {
       m_timer->stop();
   }

protected:
   void initializeGL() override {
       initializeOpenGLFunctions();
       glEnable(GL_DEPTH_TEST);
       glShadeModel(GL_SMOOTH);
       glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
       glClearDepth(1.0f);
   }

   void resizeGL(int w, int h) override {
       glViewport(0, 0, w, h);
       glMatrixMode(GL_PROJECTION);
       glLoadIdentity();
       gluPerspective(45.0f, static_cast<GLfloat>(w)/static_cast<GLfloat>(h), 0.1f, 100.0f);
       glMatrixMode(GL_MODELVIEW);
       glLoadIdentity();
   }

   void paintGL() override {
       glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
       glLoadIdentity();
       glTranslatef(0.0f, 0.0f, -10.0f);
       glRotatef(m_xRot, 1.0f, 0.0f, 0.0f);
       glRotatef(m_yRot, 0.0f, 1.0f, 0.0f);
       glRotatef(m_zRot, 0.0f, 0.0f, 1.0f);

       // 绘制频谱
       glBegin(GL_QUADS);
       for (int i = 0; i < m_spectrumData.size() / 2; ++i) {
           GLfloat x1 = static_cast<GLfloat>(i * 2);
           GLfloat y1 = static_cast<GLfloat>(m_spectrumData[i]);
           GLfloat x2 = static_cast<GLfloat>(i * 2 + 2);
           GLfloat y2 = static_cast<GLfloat>(m_spectrumData[i + 1]);
           glColor3f(y1 / 200.0f, 0.0f, y2 / 200.0f);
           glVertex3f(x1, y1, 0.0f);
           glVertex3f(x2, y2, 0.0f);
           glVertex3f(x2, 0.0f, 0.0f);
           glVertex3f(x1, 0.0f, 0.0f);
       }
       glEnd();
   }

private:
   void onTimeout() {
       // 更新数据
       for (int i = 1; i < m_data.size(); ++i) {
           m_data[i - 1] = m_data[i];
       }
       m_data[m_data.size() - 1] = rand() % 200;

       // 计算频谱
       fftw_execute(m_plan);

       // 更新图像
       m_spectrumData = m_spectrumData.mid(1);
       m_spectrumData.append(m_spectrumData.last());
       for (int i = 0; i < m_spectrumData.size(); ++i) {
           m_spectrumData[i] = static_cast<int>(m_spectrumData[i] * 0.9 + m_data[i] * 0.1);
       }

       update();
   }

   void mousePressEvent(QMouseEvent* event) override {
       m_lastPos = event->pos();
   }

   void mouseMoveEvent(QMouseEvent* event) override {
       int dx = event->x() - m_lastPos.x();
       int dy = event->y() - m_lastPos.y();

       if (event->buttons() & Qt::LeftButton) {
           m_xRot += dy;
           m_yRot += dx;
       } else if (event->buttons() & Qt::RightButton) {
           m_xRot += dy;
           m_zRot += dx;
       }

       m_lastPos = event->pos();
       update();
   }

private:
   QVector<double> m_data;
   QVector<double> m_spectrumData;
   QTimer* m_timer;
   fftw_plan m_plan;
   int m_xRot, m_yRot, m_zRot;
   QPoint m_lastPos;
};


该Demo继承自QOpenGLWidget,实现了一个用于显示3D频谱图的控件。在该控件中,使用QVector来保存频谱图中每一点的数据以及用于计算频谱的原始数据。使用QOpenGLWidget来绘制频谱图,并通过鼠标事件来控制频谱的旋转。

在绘制频谱时,我们使用OpenGL的立方体来表达每个频谱数据点。为了使图像更加美观,我们通过颜色来代表高度。代码中,我们通过调用QOpenGLFunctions类中的函数来初始化OpenGL环境,并实现了三个OpenGL事件函数,分别是initializeGL、resizeGL和paintGL。在paintGL函数中,对于每一个频率,我们以其对应的高度作为颜色的绿色通道值,然后绘制该立方体。

需要注意的是,为了使频谱更易于观察,代码还实现了鼠标拖动事件处理函数,通过鼠标拖动来控制频谱的角度,从而使频谱的变化更加立体丰富。