Hello i have a strange problem to save pixmap.
My Widget Header
public:
QPixmap *base; //Base Poses
QPixmap *Hair; //Hair
QPixmap *Composited; //Final Composition
bool compisition = false;
void Composite();
My Widget Cpp
At paintEvent
base = &pic;
Hair = &hairs;
if(compisition)
{
QPixmap pix(128,192);
QPainter *p = new QPainter(&pix);
p->drawPixmap(0,0,128,192,*base);
p->drawPixmap(0,0,128,192,*Hair);
Composited = &pix;
compisition = false;
}
void AnimPreview::Composite()
{
compisition = true;
this->update();
}
At main form source
void MainWindow::on_commandLinkButton_clicked()
{
QString file = QFileDialog::getSaveFileName(this,
tr("Save Sprite file"),
"",tr("File PNG (*.png)"));
const QPixmap *pix = ui->SpriteFront->pixmap();
if(!file.isEmpty())
{
QFile files(file);
files.open(QIODevice::WriteOnly);
ui->SpriteFront->Composite();
ui->SpriteFront->Composited->save(&files,"PNG");
}
}
When i try to save a file, the process work but whit on error
An unhandled win32 exception
For more information
complete code here
https://pastebin.com/GtaVCXGf
I have avoided reviewing where the error can be generated since there are many possible sources of the problem, among them the following:
It is not necessary that you create QPixmap pointers since in the end you will have the job of eliminating it from memory.
The same happens with QPainter since it only needs to be a local variable, in addition to that the painting is not done immediately, to be sure that it is painted you must call its end() method.
paintEvent is a protected method, so by design we prefer it to remain so.
It is not necessary to use a QFile to save an image, you can directly pass the filename to it.
Considering all the above, we obtain the following:
*.h
#ifndef ANIMPREVIEW_H
#define ANIMPREVIEW_H
#include <QLabel>
#include <QPixmap>
class AnimPreview : public QLabel
{
public:
AnimPreview(QWidget *parent = 0);
void Rotate(int value);
void Composite();
void Create(int _sex, int _hair);
QPixmap Composited;
private:
int sex = 0;
int hair = 0;
bool draw = true;
int rotation = 0;
const int offsetX = 16;
const int offsetY = 32;
QPixmap base;
QPixmap Hair;
bool compisition = false;
protected:
void paintEvent(QPaintEvent *);
};
#endif // ANIMPREVIEW_H
*.cpp
#include "animpreview.h"
#include <QPainter>
AnimPreview::AnimPreview(QWidget *parent):QLabel(parent)
{
}
void AnimPreview::paintEvent(QPaintEvent *){
QPainter p(this);
QPixmap pic;
QPixmap hairs;
if(draw)
{
//Sesso
switch(sex)
{
case 0:
pic.load(":/Male/Base/Res/man_f.png");
break;
case 1:
pic.load(":/Male/Base/Res/woman_f.png");
break;
}
//capelli
if(sex == 1)
{
switch(hair)
{
case 1:
hairs.load(":/Female/Hair/Res/7_aqua.png");
break;
case 2:
hairs.load(":/Female/Hair/Res/5_gold.png");
break;
}
}
if(sex == 0)
{
switch (hair)
{
case 0:
break;
case 1:
hairs.load(":/Male/Hair/Res/1_aqua.png");
break;
case 2:
hairs.load(":/Male/Hair/Res/14_black.png");
break;
}
}
}
p.drawPixmap(width()/2 - offsetX,height()/2 - offsetY,pic,0,rotation,32,48);
p.drawPixmap(width()/2 - offsetX,height()/2 - offsetY,hairs,0,rotation,32,48);
p.drawRect(0,0, width()-1, height()-1);
base = pic;
Hair = hairs;
if(compisition)
{
QPixmap pix(128,192);
QPainter p(&pix);
p.drawPixmap(0,0,128,192, base);
p.drawPixmap(0,0,128,192, Hair);
p.end();
Composited = pix;
compisition = false;
}
}
void AnimPreview::Rotate(int value)
{
rotation = value;
update();
}
void AnimPreview::Create(int _sex, int _hair)
{
sex = _sex;
hair = _hair;
draw = true;
}
void AnimPreview::Composite()
{
compisition = true;
update();
}
void MainWindow::on_commandLinkButton_clicked()
{
QString file = QFileDialog::getSaveFileName(this,
tr("Save Sprite file"),
"",tr("File PNG (*.png)"));
if(!file.isEmpty())
{
ui->SpriteFront->Composite();
ui->SpriteFront->Composited.save(file,"PNG");
}
}
Related
I'm subclassing the scrollAreaWidgetContents (which is literally a QWidget) of a QScrollArea and drawing an image on its background.
While resizing the widget, it was using too much CPU (tested with the QScrollArea containing no other widgets) so I tried to find a way to 'optimize' the drawing of the image, and I have written this class:
class ScrollChild : public QWidget
{
Q_OBJECT
public:
QTimer* m_resizeTimer;
int width;
int height;
QPixmap pixmap = QPixmap("...");
QPixmap pixmapScaled;
int pixWidth, prevpixWidth;
int pixHeight, prevpixHeight;
int pixmapPosX;
int pixmapPosY;
// getPos: a dynamic property set in Qt Designer to
// the widget that will be used as base to where the
// pixmap should be drawn.
QWidget* getPos;
QWidget* widget;
ScrollChild(QWidget* parent = 0) : QWidget(parent)
{
initTimer();
};
void resizeEvent(QResizeEvent* event)
{
QWidget::resizeEvent(event);
width = event->size().width();
height = event->size().height();
m_resizeTimer->start();
}
bool runtime = true;
void showEvent(QShowEvent* event)
{
if (runtime)
{
runtime = false;
// Looking for the property inside showEvent because
// when searched in the constructor its not found.
for (auto wdgt : this->findChildren<QWidget*>())
{
if (!wdgt->property("getPos").isNull())
{
getPos = wdgt;
break;
}
}
}
}
void initTimer()
{
m_resizeTimer = new QTimer(this);
m_resizeTimer->setInterval(100);
m_resizeTimer->setSingleShot(true);
connect(m_resizeTimer, &QTimer::timeout, this, &ScrollChild::resizePicture);
}
void resizePicture()
{
pixWidth = width;
pixHeight = height;
if (pixWidth > 600)
pixWidth = 600;
if (pixHeight > 400)
pixHeight = 400;
if ( (pixWidth != prevpixWidth) && (pixHeight != prevpixHeight) )
pixmapScaled = pixmap.scaled(pixWidth, pixHeight, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation);
prevpixWidth = pixWidth;
prevpixHeight = pixHeight;
//qDebug() << "w: " << width << " h: " << height;
//qDebug() << "pixmap:\nw: " << pixmap.width() << "h: " << pixmap.height() << "\n";
repaint();
}
void paintEvent(QPaintEvent *e)
{
if (pixmapScaled.isNull())
return;
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
painter.setRenderHint(QPainter::SmoothPixmapTransform);
widget = getPos;
pixmapPosX = 0;
pixmapPosY = 0;
// Search the position of the widget marked in
// the Qt Designer.
// As the widget is inside a container we must
// iterate trought her parent adding the x pos
// relative to the scrollAreaWidgetContent.
while (widget != this)
{
pixmapPosX += widget->pos().x();
pixmapPosY += widget->pos().y();
widget = widget->parentWidget();
}
painter.drawPixmap(pixmapPosX, pixmapPosY,
pixmapScaled.width(), pixmapScaled.height(),
pixmapScaled);
}
};
It reduced the CPU usage by 60%~ while keeping the same looking/quality.
Its possible to improve on something else? or write it in a most efficient way?
I construct a QImage with data I get from an API call. This QImage I will draw with the overridden paintEvent(QPaintEvent*) method into a widget. From the function that does the API call I emit a signal that sends the grabbed data to a slot that constructs the QImage. With this process there is no problem. The data is transfered and the construct of the image works.
My question is how can I use the constucted image in the overridden paintEvent? I can open a image from the disk and draw it into the widget, but this works from inside the paintEvent() method. With the API call I got the data from outside.
class GrabberClass : public QObject
{
Q_OBJECT
public:
GrabberClass(QObject* parent = nullptr);
virtual ~GrabberClass();
bool applyFrameCallback();
bool getFrameData(DtMxData* data);
bool startProcessing();
private:
RenderClass *m_renderClass;
DtMxProcess *m_process;
DTAPI_RESULT m_result;
DtFrame *m_Frame
signals:
void sendFrameData(uchar* pData, int width, int height, int bytesPerRow);
};
//GrabberClass.cpp
//constructor
GrabberClass::GrabberClass(QObject *parent) :
m_renderClass(new RenderClass)
{
const bool connected = QObject::connect(this, &GrabberClass::sendFrameData, m_renderClass, &RenderClass::receiveFrameData, Qt::DirectConnection);
Q_ASSERT(connected); //value is true.
}
bool GrabberClass::applyFrameCallback()
{
auto frameCallback = [](DataContainer* frameData, void* pContext)
{
((GrabberClass*)pContext)->getFrameData(frameData);
};
m_result = m_process->AddMatrixCbFunc(frameCallback, this);
if (m_result != DTAPI_OK) {
return false;
}
return true;
}
bool GrabberClass::grabFrame(DataContainer* frameData)
{
m_Frame = frameData->m_Rows[IN_ROW].m_CurFrame;
uchar* pData = m_Frame->m_Video->m_Planes->m_pBuf;
int bytesPerRow = m_Frame->m_Video->m_Planes->m_Stride;
int frameWidth = m_Frame->m_Video->m_Width;
int frameHeight = m_Frame->m_Video->m_Height;
emit sendFrameData(pData, frameWidth, frameHeight, bytesPerRow);
return true;
}
//Render Class.h
class RenderClass : public QOpenGLWidget {
Q_OBJECT
public:
RenderClass(QWidget* parent = nullptr);
virtual ~RenderClass();
private:
QImage m_srcImage;
protected:
void paintEvent(QPaintEvent*);
public slots:
void receiveFrameData(uchar* pData, int width, int height, int bytesPerRow);
};
//RenderClass.cpp
VideoMonitor::VideoMonitor(QWidget* parent) :
QOpenGLWidget(parent),
m_srcImage(nullptr)
{
}
void RenderClass::receiveFrameData(uchar* pData, int width, int height, int bytesPerRow)
{
m_srcImage = QImage(pData, width, height, bytesPerRow, QImage::Format_Indexed8);
qDebug() << "Slot is called." << m_srcImage; //QImage is constructed with valid data
QWidget::update();
}
void RenderClass::paintEvent(QPaintEvent*)
{
if (!m_srcImage.isNull()) { //m_srcImage is null
QPainter painter(this);
painter.drawImage(this->rect(), m_srcImage);
painter.end();
qDebug() << "Draw image.";
}
else {
qDebug() << "QImage is null";
}
}
I'am new to C++, so it would be nice if someone of you can give me an advice and explaination.
The project im trying to build is a media player with c++ and qt inclusion. However im failing to do this due to error , which i am not sure why or how it occurs. Please excuse me since this is my first ever project with c ++ or QT .
The Error is multiple defintions of frames for the media player which i am not sure because in header file only definitions are written and source (cpp) the code is written . I would really appreciate the help , thank you everyone for taking time of your day to help means a lot.
Code:
playeframe.h
#include <QAbstractVideoSurface>
#include <QImage>
#include <QVideoFrame>
#include <QLabel>
class PlayerFrame : public QAbstractVideoSurface
{
Q_OBJECT
public:
PlayerFrame(QObject *parent = nullptr);
void stopVideo();
Q_SIGNALS:
void fnSurfaceStopped(QPixmap pix);
private slots:
void fnClearPixmap();
private:
QPixmap CapImage;
QImage::Format imgFormat;
QVideoFrame curFrame;
bool Videostart(const QVideoSurfaceFormat &format);
QList<QVideoFrame::PixelFormat> supportedPixelFormats(QAbstractVideoBuffer::HandleType handleType) const;
bool isFormatSupported(const QVideoSurfaceFormat &format) const;
bool present(const QVideoFrame &frame);
};
#endif
playerframe.cpp
#include "playerframe.h"
#include <QtWidgets>
#include <qvideosurfaceformat.h>
#include "playerwindow.h"
PlayerFrame::PlayerFrame(QObject *parent)
: QAbstractVideoSurface(parent)
, imgFormat(QImage::Format_Invalid)
{
}
bool PlayerFrame::Videostart(const QVideoSurfaceFormat &format)
{
QImage::Format imageFormat = QVideoFrame::imageFormatFromPixelFormat(format.pixelFormat());
QSize size = format.frameSize();
if (imageFormat != QImage::Format_Invalid && !size.isEmpty())
{
this->imgFormat = imageFormat;
QAbstractVideoSurface::start(format);
return true;
}
else return false;
}
void PlayerFrame::fnClearPixmap()
{
CapImage = QPixmap();
}
void PlayerFrame::stopVideo()
{
QAbstractVideoSurface::stop();
}
QList<QVideoFrame::PixelFormat> PlayerFrame::supportedPixelFormats(QAbstractVideoBuffer::HandleType handleType) const
{
if (handleType == QAbstractVideoBuffer::NoHandle) {
return QList<QVideoFrame::PixelFormat>()
<< QVideoFrame::Format_RGB32
<< QVideoFrame::Format_ARGB32
<< QVideoFrame::Format_ARGB32_Premultiplied
<< QVideoFrame::Format_RGB565
<< QVideoFrame::Format_RGB555;
}
else
{
return QList<QVideoFrame::PixelFormat>();
}
}
bool PlayerFrame::isFormatSupported(const QVideoSurfaceFormat &format) const
{
QImage::Format imageFormat = QVideoFrame::imageFormatFromPixelFormat(format.pixelFormat());
QSize size = format.frameSize();
return imageFormat != QImage::Format_Invalid && !size.isEmpty() && format.handleType() == QAbstractVideoBuffer::NoHandle;
}
bool PlayerFrame::present(const QVideoFrame &frame)
{
if (surfaceFormat().pixelFormat() != frame.pixelFormat() || surfaceFormat().frameSize() != frame.size())
{
setError(IncorrectFormatError);
stop();
return false;
}
else
{
if (!CapImage.isNull())
{
fnSurfaceStopped(CapImage);
}
curFrame = frame;
if (curFrame.map(QAbstractVideoBuffer::ReadOnly))
{
QImage image(
curFrame.bits(),
curFrame.width(),
curFrame.height(),
curFrame.bytesPerLine(),
imgFormat);
if (CapImage.isNull())
{
CapImage = QPixmap::fromImage(image.copy(image.rect()));
}
curFrame.unmap();
}
return true;
}
}
I want to copy the selected column of a QTableWidget to another one.
So I tried to make selected columns draggable by adding this code:
void makeDraggable(QTableWidget *table)
{
table->setDragEnabled(true);
table->setAcceptDrops(true);
table->setSelectionBehavior(QAbstractItemView::SelectColumns);
}
Result I got:
But I want to drag a whole column (horizontal and vertical headers) by clicking on headers only, not on cells, and copy its data to another table including the header text.
Dragging between different tables inside one application can be done with reimplementing custom QHeaderView and QTableWidget. In my example I generate text with indecies of table and column for drag event. Custom header:
#include <QHeaderView>
class ITableManager;
class DraggableHeaderView : public QHeaderView
{
Q_OBJECT
public:
explicit DraggableHeaderView(Qt::Orientation orientation, QWidget *parent = 0);
int tag() const;
void setTag(const int tag);
void setTableManager(ITableManager* manager);
protected:
void mouseMoveEvent(QMouseEvent *e);
void dragEnterEvent(QDragEnterEvent *event);
void dragMoveEvent(QDragMoveEvent *event);
void dropEvent(QDropEvent *event);
signals:
public slots:
private:
int m_tag; //internal index of table
ITableManager *m_tableManager; //manager will convert table index into pointer
};
Custom header cpp
#include <QMouseEvent>
#include <QDrag>
#include <QMimeData>
#include <QDebug>
#include <QTableWidget>
#include <ITableManager.h>
DraggableHeaderView::DraggableHeaderView(Qt::Orientation orientation, QWidget *parent) :
QHeaderView(orientation, parent)
{
m_tag = 0;
m_tableManager = 0;
setAcceptDrops(true);
}
void DraggableHeaderView::mouseMoveEvent(QMouseEvent *e)
{
if (e->buttons() & Qt::LeftButton)
{
int index = logicalIndexAt(e->pos());
QDrag *drag = new QDrag(this);
QMimeData *mimeData = new QMimeData;
//custom drag text with indecies inside
QString mimeTxt = "MoveHeader;Table:" + QString::number(m_tag) +
";Index:" + QString::number(index);
mimeData->setText(mimeTxt);
drag->setMimeData(mimeData);
Qt::DropAction dropAction = drag->exec();
}
}
int DraggableHeaderView::tag() const
{
return m_tag;
}
void DraggableHeaderView::setTag(const int tag)
{
m_tag = tag;
}
void DraggableHeaderView::dragEnterEvent(QDragEnterEvent *event)
{
if (!m_tableManager)
{
event->ignore();
return;
}
QString dragText = event->mimeData()->text();
int index = dragText.indexOf("MoveHeader;");
if (index == 0)
{
event->accept();
}
else
{
event->ignore();
}
}
void DraggableHeaderView::dropEvent(QDropEvent *event)
{
if (!m_tableManager)
{
event->ignore();
return;
}
QStringList dragText = event->mimeData()->text().split(';');
if (dragText.count() < 3 || dragText.at(0) != "MoveHeader")
{
event->ignore();
return;
}
int tableIndex = dragText.at(1).mid(6).toInt();//6 - length 'Table:'
QTableWidget* tableSrc = m_tableManager->getTableFromIndex(tableIndex);
if (!tableSrc)
{
event->ignore();
return;
}
//dst table as parent for header view
QTableWidget *tableDst = qobject_cast<QTableWidget*> (this->parentWidget());
if (!tableDst)
{
event->ignore();
return;
}
//move column: modify for your needs
//now moves only items text
int columnIndex = logicalIndexAt(event->pos());
int srcColumnIndex = dragText.at(2).mid(6).toInt(); //6 - length of 'Index:'
tableDst->insertColumn(columnIndex);
for (int iRow = 0; iRow < tableDst->rowCount() && iRow < tableSrc->rowCount(); ++iRow)
{
if (tableSrc->item(iRow, srcColumnIndex))
{
tableDst->setItem(iRow, columnIndex,
new QTableWidgetItem(tableSrc->item(iRow, srcColumnIndex)->text()));
}
else
{
tableDst->setItem(iRow, columnIndex, new QTableWidgetItem());
}
}
tableSrc->removeColumn(srcColumnIndex);
}
void DraggableHeaderView::setTableManager(ITableManager *manager)
{
m_tableManager = manager;
}
Now create custom QTableWidget with DraggableHeaderView inside
class CustomTableWidget : public QTableWidget
{
Q_OBJECT
public:
explicit CustomTableWidget(QWidget *parent = 0);
void setTag(const int tag);
void setTableManager(ITableManager* manager);
};
CustomTableWidget::CustomTableWidget(QWidget *parent) :
QTableWidget(parent)
{
DraggableHeaderView *headerView = new DraggableHeaderView(Qt::Horizontal, this);
setHorizontalHeader(headerView);
setAcceptDrops(true);
}
void CustomTableWidget::setTag(const int tag)
{
DraggableHeaderView *header = qobject_cast<DraggableHeaderView*> (horizontalHeader());
if (header)
{
header->setTag(tag);
}
}
void CustomTableWidget::setTableManager(ITableManager *manager)
{
DraggableHeaderView *header = qobject_cast<DraggableHeaderView*> (horizontalHeader());
if (header)
{
header->setTableManager(manager);
}
}
For converting table index to pointer I use ITableManager
class ITableManager
{
public:
virtual QTableWidget* getTableFromIndex(const int index) = 0;
};
And implement it in QMainWindow
class MainWindow : public QMainWindow, ITableManager
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
QTableWidget* getTableFromIndex(const int index);
}
QTableWidget * MainWindow::getTableFromIndex(const int index)
{
switch (index)
{
case 1:
return ui->tableWidget;
case 2:
return ui->tableWidget_2;
default:
return nullptr;
}
}
Dont forget setup tags (indecies) and table manager for tables (in main window constructor)
ui->tableWidget->setTag(1);
ui->tableWidget_2->setTag(2);
ui->tableWidget->setTableManager(this);
ui->tableWidget_2->setTableManager(this);
EDIT: If you want change custom pixmap for dragging just set QDrag::setPixmap
void DraggableHeaderView::mouseMoveEvent(QMouseEvent *e)
{
if (e->buttons() & Qt::LeftButton)
{
int index = logicalIndexAt(e->pos());
QDrag *drag = new QDrag(this);
QMimeData *mimeData = new QMimeData;
QString mimeTxt = "MoveHeader;Table:" + QString::number(m_tag) +
";Index:" + QString::number(index);
mimeData->setText(mimeTxt);
drag->setMimeData(mimeData);
drag->setPixmap(pixmapForDrag(index));
Qt::DropAction dropAction = drag->exec();
}
}
And method for taking pixmap of column can be like this
QPixmap DraggableHeaderView::pixmapForDrag(const int columnIndex) const
{
QTableWidget *table = qobject_cast<QTableWidget*> (this->parentWidget());
if (!table)
{
return QPixmap();
}
//image for first 5 row
int height = table->horizontalHeader()->height();
for (int iRow = 0; iRow < 5 && iRow < table->rowCount(); ++iRow)
{
height += table->rowHeight(iRow);
}
//clip maximum size
if (height > 200)
{
height = 200;
}
QRect rect(table->columnViewportPosition(columnIndex) + table->verticalHeader()->width(),
table->rowViewportPosition(0),
table->columnWidth(columnIndex),
height);
QPixmap pixmap(rect.size());
table->render(&pixmap, QPoint(), QRegion(rect));
return pixmap;
}
I have to manage movie and audio file and I need to render a wave of the sound like in audacity. But I just find example for realtime render.
I want to render all the file without playing it.
Expected result:
My actual result:
With Qt I tried to use QAudioDecoder to open my file and get a QAudioBuffer but I don't find an algorithm to transform all the data into wave. I also try to see with the Qt Spectrum Example but it's not trivial to understand and it's still in realtime.
My track.h:
#ifndef TRACK_H
#define TRACK_H
#include <QWidget>
#include <QAudioBuffer>
class QAudioDecoder;
class Track : public QWidget
{
Q_OBJECT
public:
Track(QWidget *parent = Q_NULLPTR);
~Track();
void setSource(const QString &fileName);
public slots:
void setBuffer();
protected:
void paintEvent(QPaintEvent *e) override;
private:
int pointDistance(const QPoint& a, const QPoint& b);
QAudioDecoder *decoder;
QAudioBuffer buffer;
QByteArray byteArr;
};
#endif // TRACK_H
My track.cpp:
#include "track.h"
#include <QPaintEvent>
#include <QPainter>
#include <QAudioDecoder>
Track::Track(QWidget *parent)
: QWidget(parent)
, decoder(new QAudioDecoder(this))
{
setMinimumHeight(50);
connect(decoder, SIGNAL(bufferReady()), this, SLOT(setBuffer()));
connect(decoder, SIGNAL(finished()), this, SLOT(update()));
}
Track::~Track()
{
delete decoder;
}
void Track::setSource(const QString &fileName)
{
byteArr.clear();
decoder->setSourceFilename(fileName);
decoder->start();
}
void Track::setBuffer()
{
buffer = decoder->read();
byteArr.append(buffer.constData<char>(), buffer.byteCount());
}
void Track::paintEvent(QPaintEvent *e)
{
QWidget::paintEvent(e);
int w = width(), h = height();
QBrush backgroundBrush(Qt::white);
QPainter painter(this);
painter.fillRect(0, 0, w, h, backgroundBrush);
painter.drawLine(0, h/2, w, h/2);
if (!byteArr.isEmpty()){
QPen pen(QColor(Qt::blue));
painter.setPen(pen);
int length = byteArr.size();
int samplesPerPixel = length/w;
int idx=0;
for (int i=0; i<w; i++){
QLine line;
int higher = 0;
for (int j=0; j<samplesPerPixel && idx+1<length; j++){
const QPoint a(i, byteArr.at(idx)+(h/2));
const QPoint b(i, byteArr.at(idx+1)+(h/2));
if (higher < pointDistance(a, b))
line = QLine(a, b);
idx++;
}
painter.drawLine(line);
}
}
}
int Track::pointDistance(const QPoint &a, const QPoint &b)
{
int ret = 0;
ret = sqrt(pow(b.x()-a.x(), 2) + pow(b.y()-a.y(), 2));
return ret;
}
I finally found a solution by using QCustomPlot widget (by reading this post):
My result:
My track.h:
#ifndef TRACK_H
#define TRACK_H
#include "qcustomplot.h"
#include <QAudioBuffer>
class QAudioDecoder;
class Track : public QCustomPlot
{
Q_OBJECT
public:
Track(TrackType type, QWidget *parent = Q_NULLPTR);
~Track();
void setSource(const QString &fileName);
public slots:
void setBuffer();
void plot();
private:
qreal getPeakValue(const QAudioFormat& format);
QAudioDecoder *decoder;
QAudioBuffer buffer;
QVector<double> samples;
QCPGraph *wavePlot;
};
#endif // TRACK_H
My track.cpp:
#include "track.h"
#include <QAudioDecoder>
Track::Track(Track::TrackType type, QWidget *parent)
: QCustomPlot(parent)
, decoder(new QAudioDecoder(this))
{
this->type = type;
wavePlot = addGraph();
setMinimumHeight(50);
connect(decoder, SIGNAL(bufferReady()), this, SLOT(setBuffer()));
connect(decoder, SIGNAL(finished()), this, SLOT(plot()));
}
Track::~Track()
{
delete decoder;
// wavePlot delete auto ?
}
void Track::setSource(const QString &fileName)
{
samples.clear();
decoder->setSourceFilename(fileName);
decoder->start();
}
void Track::setBuffer()
{
buffer = decoder->read();
qreal peak = getPeakValue(buffer.format());
const qint16 *data = buffer.constData<qint16>();
int count = buffer.sampleCount() / 2;
for (int i=0; i<count; i++){
double val = data[i]/peak;
samples.append(val);
}
}
void Track::plot()
{
QVector<double> x(samples.size());
for (int i=0; i<x.size(); i++)
x[i] = i;
wavePlot->addData(x, samples);
yAxis->setRange(QCPRange(-1, 1));
xAxis->setRange(QCPRange(0, samples.size()));
replot();
}
/**
* https://stackoverflow.com/questions/46947668/draw-waveform-from-raw-data-using-qaudioprobe
* #brief Track::getPeakValue
* #param format
* #return The peak value
*/
qreal Track::getPeakValue(const QAudioFormat &format)
{
qreal ret(0);
if (format.isValid()){
switch (format.sampleType()) {
case QAudioFormat::Unknown:
break;
case QAudioFormat::Float:
if (format.sampleSize() != 32) // other sample formats are not supported
ret = 0;
else
ret = 1.00003;
break;
case QAudioFormat::SignedInt:
if (format.sampleSize() == 32)
#ifdef Q_OS_WIN
ret = INT_MAX;
#endif
#ifdef Q_OS_UNIX
ret = SHRT_MAX;
#endif
else if (format.sampleSize() == 16)
ret = SHRT_MAX;
else if (format.sampleSize() == 8)
ret = CHAR_MAX;
break;
case QAudioFormat::UnSignedInt:
if (format.sampleSize() == 32)
ret = UINT_MAX;
else if (format.sampleSize() == 16)
ret = USHRT_MAX;
else if (format.sampleSize() == 8)
ret = UCHAR_MAX;
break;
default:
break;
}
}
return ret;
}