一 设计思路
Qt绘制控件时,会在主线程调用paintEvent方法,将图像渲染到屏幕上。但是我们有时需要绘制复杂的图形,此时再在主线程中进行绘制,就会造成界面卡顿。有一种优化思路是,在其他线程上面做离屏渲染,主线程将离屏渲染的结果渲染到屏幕上面。
二 示例
meter.h
#pragma once
#include <QtGui>
#include <QFrame>
#include <QImage>
class CMeter : public QFrame
{
Q_OBJECT
public:
explicit CMeter(QWidget *parent = 0);
explicit CMeter(float min_level = -100.0, float max_level = 10.0,
QWidget *parent = 0);
~CMeter();
QSize minimumSizeHint() const;
QSize sizeHint() const;
void setMin(float min_level);
void setMax(float max_level);
void setRange(float min_level, float max_level);
void draw();
void UpdateOverlay(){DrawOverlay();}
public slots:
void setLevel(float dbfs);
void setSqlLevel(float dbfs);
protected:
void paintEvent(QPaintEvent *event);
void resizeEvent(QResizeEvent* event);
private:
void DrawOverlay();
QFont m_Font;
QPixmap m_2DPixmap;
QPixmap m_OverlayPixmap;
QSize m_Size;
QString m_Str;
qreal m_pixperdb; // pixels / dB
qreal m_Siglevel;
float m_dBFS;
qreal m_Sql;
qreal m_SqlLevel;
};
meter.cpp
#include <cmath>
#include <QDebug>
#include "meter.h"
// ratio to total control width or height
#define CTRL_MARGIN 0.07 // left/right margin
#define CTRL_MAJOR_START 0.3 // top of major tic line
#define CTRL_MINOR_START 0.3 // top of minor tic line
#define CTRL_XAXIS_HEGHT 0.4 // vertical position of horizontal axis
#define CTRL_NEEDLE_TOP 0.4 // vertical position of top of needle triangle
#define MIN_DB -100.0f
#define MAX_DB +0.0f
#define ALPHA_DECAY 0.25f
#define ALPHA_RISE 0.70f
CMeter::CMeter(QWidget *parent) : QFrame(parent)
{
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
setFocusPolicy(Qt::StrongFocus);
setAttribute(Qt::WA_PaintOnScreen,false);
setAutoFillBackground(false);
setAttribute(Qt::WA_OpaquePaintEvent, false);
setAttribute(Qt::WA_NoSystemBackground, true);
setMouseTracking(true);
m_Font = QFont("Arial");
m_Font.setWeight(QFont::Normal);
m_2DPixmap = QPixmap(0,0);
m_OverlayPixmap = QPixmap(0,0);
m_Size = QSize(0,0);
m_pixperdb = 0.0;
m_Siglevel = 0;
m_dBFS = MIN_DB;
m_Sql = -150.0;
m_SqlLevel = 0.0;
}
CMeter::~CMeter()
{
}
QSize CMeter::minimumSizeHint() const
{
return QSize(20, 10);
}
QSize CMeter::sizeHint() const
{
return QSize(100, 30);
}
void CMeter::resizeEvent(QResizeEvent *)
{
if (!size().isValid())
return;
if (m_Size != size())
{
// if size changed, resize pixmaps to new screensize
m_Size = size();
qreal dpr = devicePixelRatioF();
m_OverlayPixmap = QPixmap(m_Size.width() * dpr, m_Size.height() * dpr);
m_OverlayPixmap.setDevicePixelRatio(dpr);
m_OverlayPixmap.fill(Qt::black);
m_2DPixmap = QPixmap(m_Size.width() * dpr, m_Size.height() * dpr);
m_2DPixmap.setDevicePixelRatio(dpr);
m_2DPixmap.fill(Qt::black);
qreal w = (m_2DPixmap.width() / dpr) - 2 * CTRL_MARGIN * (m_2DPixmap.width() / dpr);
m_pixperdb = w / fabs((double)(MAX_DB - MIN_DB));
setSqlLevel(m_Sql);
}
DrawOverlay();
draw();
}
void CMeter::setLevel(float dbfs)
{
if (dbfs < MIN_DB)
dbfs = MIN_DB;
else if (dbfs > MAX_DB)
dbfs = MAX_DB;
float level = m_dBFS;
float alpha = dbfs < level ? ALPHA_DECAY : ALPHA_RISE;
m_dBFS -= alpha * (level - dbfs);
m_Siglevel = (qreal)(level - MIN_DB) * m_pixperdb;
draw();
}
void CMeter::setSqlLevel(float dbfs)
{
if (dbfs >= 0.f)
m_SqlLevel = 0.0;
else
m_SqlLevel = (qreal)(dbfs - MIN_DB) * m_pixperdb;
if (m_SqlLevel < 0.0)
m_SqlLevel = 0.0;
m_Sql = (qreal)dbfs;
}
// Called by QT when screen needs to be redrawn
void CMeter::paintEvent(QPaintEvent *)
{
QPainter painter(this);
painter.drawPixmap(0, 0, m_2DPixmap);
return;
}
// Called to update s-meter data for displaying on the screen
void CMeter::draw()
{
int w;
int h;
if (m_2DPixmap.isNull())
return;
// get/draw the 2D spectrum
w = m_2DPixmap.width() / m_2DPixmap.devicePixelRatioF();
h = m_2DPixmap.height() / m_2DPixmap.devicePixelRatioF();
// first copy into 2Dbitmap the overlay bitmap.
m_2DPixmap = m_OverlayPixmap.copy(0, 0, m_OverlayPixmap.width(), m_OverlayPixmap.height());
QPainter painter(&m_2DPixmap);
// DrawCurrent position indicator
qreal hline = (qreal) h * CTRL_XAXIS_HEGHT;
qreal marg = (qreal) w * CTRL_MARGIN;
qreal ht = (qreal) h * CTRL_NEEDLE_TOP;
qreal x = marg + m_Siglevel;
if (m_Siglevel > 0.0)
{
QColor color(0, 190, 0, 255);
QPen pen(color);
pen.setJoinStyle(Qt::MiterJoin);
painter.setPen(pen);
painter.setBrush(QBrush(color));
painter.setOpacity(1.0);
painter.drawRect(QRectF(marg, ht + 2, x - marg, 4));
}
if (m_SqlLevel > 0.0)
{
x = marg + m_SqlLevel;
painter.setPen(QPen(Qt::yellow, 1, Qt::SolidLine));
painter.drawLine(QLineF(x, hline, x, hline + 8));
}
int y = (h) / 4;
m_Font.setPixelSize(y);
painter.setFont(m_Font);
painter.setPen(QColor(0xDA, 0xDA, 0xDA, 0xFF));
painter.setOpacity(1.0);
m_Str.setNum(m_dBFS, 'f', 1);
painter.drawText(marg, h - 2, m_Str + " dBFS" );
update();
}
// Called to draw an overlay bitmap containing items that
// does not need to be recreated every fft data update.
void CMeter::DrawOverlay()
{
if (m_OverlayPixmap.isNull())
return;
int w = m_OverlayPixmap.width() / m_OverlayPixmap.devicePixelRatioF();
int h = m_OverlayPixmap.height() / m_OverlayPixmap.devicePixelRatioF();
int x,y;
QRect rect;
QPainter painter(&m_OverlayPixmap);
m_OverlayPixmap.fill(QColor(0x1F, 0x1D, 0x1D, 0xFF));
// Draw scale lines
qreal marg = (qreal) w * CTRL_MARGIN;
qreal hline = (qreal)h * CTRL_XAXIS_HEGHT;
qreal magstart = (qreal) h * CTRL_MAJOR_START;
qreal minstart = (qreal) h * CTRL_MINOR_START;
qreal hstop = (qreal) w - marg;
painter.setPen(QPen(Qt::white, 1, Qt::SolidLine));
painter.drawLine(QLineF(marg, hline, hstop, hline)); // top line
painter.drawLine(QLineF(marg, hline+8, hstop, hline+8)); // bottom line
qreal xpos = marg;
for (x = 0; x < 11; x++) {
if (x & 1)
//minor tics
painter.drawLine(QLineF(xpos, minstart, xpos, hline));
else
painter.drawLine(QLineF(xpos, magstart, xpos, hline));
xpos += (hstop-marg) / 10.0;
}
// draw scale text
y = h / 4;
m_Font.setPixelSize(y);
painter.setFont(m_Font);
int rwidth = (int)((hstop - marg) / 5.0);
m_Str = "-100";
rect.setRect(marg / 2 - 5, 0, rwidth, magstart);
for (x = MIN_DB; x <= MAX_DB; x += 20)
{
m_Str.setNum(x);
painter.drawText(rect, Qt::AlignHCenter|Qt::AlignVCenter, m_Str);
rect.translate(rwidth, 0);
}
}