How to QVideoFrame to QImage - qt

The task is to copy a frame from a QVideoFrame, and possibly do something to that Image and displaying the manipulated Image in QML.
...
m_lastFrame = QImage(videoFrame.width(), videoFrame.height(), QImage::Format_ARGB32);
memcpy(m_lastFrame.bits(), videoFrame.bits(),videoFrame.mappedBytes());
...
The above code causes a crash, since m_lastFrame is short of 32 bytes(3686400 vs 3686432)
videoFrame.mappedBytes() reports 3686432 bytes. What am I doing wrong here? Or how should I calculate the size of m_lastFrame().
The code is running on Mac OSx 10.9.5 Qt 5.1.1.
Some additional code:
...
if( videoFrame.map(QAbstractVideoBuffer::ReadOnly) ){
m_lastFrame = QImage(videoFrame.width(),videoFrame.height(),QImage::Format_ARGB32);
memcpy(m_lastFrame.bits(), videoFrame.bits(),videoFrame.mappedBytes() - 32);
...
}
...

Since that doesn't always work, see also comment at convert QVideoFrame to QImage , i.e.
QImage Camera::imageFromVideoFrame(const QVideoFrame& buffer) const
{
QImage img;
QVideoFrame frame(buffer); // make a copy we can call map (non-const) on
frame.map(QAbstractVideoBuffer::ReadOnly);
QImage::Format imageFormat = QVideoFrame::imageFormatFromPixelFormat(
frame.pixelFormat());
// BUT the frame.pixelFormat() is QVideoFrame::Format_Jpeg, and this is
// mapped to QImage::Format_Invalid by
// QVideoFrame::imageFormatFromPixelFormat
if (imageFormat != QImage::Format_Invalid) {
img = QImage(frame.bits(),
frame.width(),
frame.height(),
// frame.bytesPerLine(),
imageFormat);
} else {
// e.g. JPEG
int nbytes = frame.mappedBytes();
img = QImage::fromData(frame.bits(), nbytes);
}
frame.unmap();
return img;
}

You can try creating a QImage by first mapping the QVideoFrame onto a QAbstractVideoBuffer in the following way :
bool CameraFrameGrabber::present(const QVideoFrame &frame)
{
Q_UNUSED(frame);
if (frame.isValid()) {
QVideoFrame cloneFrame(frame);
cloneFrame.map(QAbstractVideoBuffer::ReadOnly);
const QImage image(cloneFrame.bits(),
cloneFrame.width(),
cloneFrame.height(),
QVideoFrame::imageFormatFromPixelFormat(cloneFrame .pixelFormat()));
emit frameAvailable(image);
qDebug()<<cloneFrame.mappedBytes();
cloneFrame.unmap();
return true;
}
If you want QImage in any other format just change the last parameter during creating the image, to the whichever format you like :
QImage::Format_xxx ;
instead of
QVideoFrame::imageFormatFromPixelFormat(cloneFrame .pixelFormat()));

Related

How to save a frame using QMediaPlayer?

I want to save an image of a frame from a QMediaPlayer. After reading the documentation, I understood that I should use QVideoProbe. I am using the following code :
QMediaPlayer *player = new QMediaPlayer();
QVideoProbe *probe = new QVideoProbe;
connect(probe, SIGNAL(videoFrameProbed(QVideoFrame)), this, SLOT(processFrame(QVideoFrame)));
qDebug()<<probe->setSource(player); // Returns true, hopefully.
player->setVideoOutput(myVideoSurface);
player->setMedia(QUrl::fromLocalFile("observation.mp4"));
player->play(); // Start receving frames as they get presented to myVideoSurface
But unfortunately, probe->setSource(player) always returns false for me, and thus my slot processFrame is not triggered.
What am I doing wrong ? Does anybody have a working example of QVideoProbe ?
You're not doing anything wrong. As #DYangu pointed out, your media object instance does not support monitoring video. I had the same problem (and same for QAudioProbe but it doesn't interest us here). I found a solution by looking at this answer and this one.
The main idea is to subclass QAbstractVideoSurface. Once you've done that, it will call the method QAbstractVideoSurface::present(const QVideoFrame & frame) of your implementation of QAbstractVideoSurface and you will be able to process the frames of your video.
As it is said here, usually you will just need to reimplement two methods :
supportedPixelFormats so that the producer can select an appropriate format for the QVideoFrame
present which allows to display the frame
But at the time, I searched in the Qt source code and happily found this piece of code which helped me to do a full implementation. So, here is the full code for using a "video frame grabber".
VideoFrameGrabber.cpp :
#include "VideoFrameGrabber.h"
#include <QtWidgets>
#include <qabstractvideosurface.h>
#include <qvideosurfaceformat.h>
VideoFrameGrabber::VideoFrameGrabber(QWidget *widget, QObject *parent)
: QAbstractVideoSurface(parent)
, widget(widget)
, imageFormat(QImage::Format_Invalid)
{
}
QList<QVideoFrame::PixelFormat> VideoFrameGrabber::supportedPixelFormats(QAbstractVideoBuffer::HandleType handleType) const
{
Q_UNUSED(handleType);
return QList<QVideoFrame::PixelFormat>()
<< QVideoFrame::Format_ARGB32
<< QVideoFrame::Format_ARGB32_Premultiplied
<< QVideoFrame::Format_RGB32
<< QVideoFrame::Format_RGB24
<< QVideoFrame::Format_RGB565
<< QVideoFrame::Format_RGB555
<< QVideoFrame::Format_ARGB8565_Premultiplied
<< QVideoFrame::Format_BGRA32
<< QVideoFrame::Format_BGRA32_Premultiplied
<< QVideoFrame::Format_BGR32
<< QVideoFrame::Format_BGR24
<< QVideoFrame::Format_BGR565
<< QVideoFrame::Format_BGR555
<< QVideoFrame::Format_BGRA5658_Premultiplied
<< QVideoFrame::Format_AYUV444
<< QVideoFrame::Format_AYUV444_Premultiplied
<< QVideoFrame::Format_YUV444
<< QVideoFrame::Format_YUV420P
<< QVideoFrame::Format_YV12
<< QVideoFrame::Format_UYVY
<< QVideoFrame::Format_YUYV
<< QVideoFrame::Format_NV12
<< QVideoFrame::Format_NV21
<< QVideoFrame::Format_IMC1
<< QVideoFrame::Format_IMC2
<< QVideoFrame::Format_IMC3
<< QVideoFrame::Format_IMC4
<< QVideoFrame::Format_Y8
<< QVideoFrame::Format_Y16
<< QVideoFrame::Format_Jpeg
<< QVideoFrame::Format_CameraRaw
<< QVideoFrame::Format_AdobeDng;
}
bool VideoFrameGrabber::isFormatSupported(const QVideoSurfaceFormat &format) const
{
const QImage::Format imageFormat = QVideoFrame::imageFormatFromPixelFormat(format.pixelFormat());
const QSize size = format.frameSize();
return imageFormat != QImage::Format_Invalid
&& !size.isEmpty()
&& format.handleType() == QAbstractVideoBuffer::NoHandle;
}
bool VideoFrameGrabber::start(const QVideoSurfaceFormat &format)
{
const QImage::Format imageFormat = QVideoFrame::imageFormatFromPixelFormat(format.pixelFormat());
const QSize size = format.frameSize();
if (imageFormat != QImage::Format_Invalid && !size.isEmpty()) {
this->imageFormat = imageFormat;
imageSize = size;
sourceRect = format.viewport();
QAbstractVideoSurface::start(format);
widget->updateGeometry();
updateVideoRect();
return true;
} else {
return false;
}
}
void VideoFrameGrabber::stop()
{
currentFrame = QVideoFrame();
targetRect = QRect();
QAbstractVideoSurface::stop();
widget->update();
}
bool VideoFrameGrabber::present(const QVideoFrame &frame)
{
if (frame.isValid())
{
QVideoFrame cloneFrame(frame);
cloneFrame.map(QAbstractVideoBuffer::ReadOnly);
const QImage image(cloneFrame.bits(),
cloneFrame.width(),
cloneFrame.height(),
QVideoFrame::imageFormatFromPixelFormat(cloneFrame .pixelFormat()));
emit frameAvailable(image); // this is very important
cloneFrame.unmap();
}
if (surfaceFormat().pixelFormat() != frame.pixelFormat()
|| surfaceFormat().frameSize() != frame.size()) {
setError(IncorrectFormatError);
stop();
return false;
} else {
currentFrame = frame;
widget->repaint(targetRect);
return true;
}
}
void VideoFrameGrabber::updateVideoRect()
{
QSize size = surfaceFormat().sizeHint();
size.scale(widget->size().boundedTo(size), Qt::KeepAspectRatio);
targetRect = QRect(QPoint(0, 0), size);
targetRect.moveCenter(widget->rect().center());
}
void VideoFrameGrabber::paint(QPainter *painter)
{
if (currentFrame.map(QAbstractVideoBuffer::ReadOnly)) {
const QTransform oldTransform = painter->transform();
if (surfaceFormat().scanLineDirection() == QVideoSurfaceFormat::BottomToTop) {
painter->scale(1, -1);
painter->translate(0, -widget->height());
}
QImage image(
currentFrame.bits(),
currentFrame.width(),
currentFrame.height(),
currentFrame.bytesPerLine(),
imageFormat);
painter->drawImage(targetRect, image, sourceRect);
painter->setTransform(oldTransform);
currentFrame.unmap();
}
}
VideoFrameGrabber.h
#ifndef VIDEOFRAMEGRABBER_H
#define VIDEOFRAMEGRABBER_H
#include <QtWidgets>
class VideoFrameGrabber : public QAbstractVideoSurface
{
Q_OBJECT
public:
VideoFrameGrabber(QWidget *widget, QObject *parent = 0);
QList<QVideoFrame::PixelFormat> supportedPixelFormats(
QAbstractVideoBuffer::HandleType handleType = QAbstractVideoBuffer::NoHandle) const;
bool isFormatSupported(const QVideoSurfaceFormat &format) const;
bool start(const QVideoSurfaceFormat &format);
void stop();
bool present(const QVideoFrame &frame);
QRect videoRect() const { return targetRect; }
void updateVideoRect();
void paint(QPainter *painter);
private:
QWidget *widget;
QImage::Format imageFormat;
QRect targetRect;
QSize imageSize;
QRect sourceRect;
QVideoFrame currentFrame;
signals:
void frameAvailable(QImage frame);
};
#endif //VIDEOFRAMEGRABBER_H
Note : in the .h, you will see I added a signal taking an image as a parameter. This will allow you to process your frame anywhere in your code. At the time, this signal took a QImage as a parameter, but you can of course take a QVideoFrame if you want to.
Now, we are ready to use this video frame grabber:
QMediaPlayer* player = new QMediaPlayer(this);
// no more QVideoProbe
VideoFrameGrabber* grabber = new VideoFrameGrabber(this);
player->setVideoOutput(grabber);
connect(grabber, SIGNAL(frameAvailable(QImage)), this, SLOT(processFrame(QImage)));
Now you just have to declare a slot named processFrame(QImage image) and you will receive a QImage each time you will enter the method present of your VideoFrameGrabber.
I hope that this will help you!
After Qt QVideoProbe documentation:
bool QVideoProbe::setSource(QMediaObject *mediaObject)
Starts monitoring the given mediaObject.
If there is no media object associated with mediaObject, or if it is
zero, this probe will be deactivated and this function wil return
true.
If the media object instance does not support monitoring video, this
function will return false.
Any previously monitored objects will no longer be monitored. Passing
in the same object will be ignored, but monitoring will continue.
So it seems your "media object instance does not support monitoring video"
TL;DR: https://gist.github.com/JC3/a7bab65acbd7659d1e57103d2b0021ba (only file)
I had a similar issue (5.15.2; although in my case I was on Windows, was definitely using the DirectShow back-end, the probe attachment was returning true, the sample grabber was in the graph, but the callback wasn't firing).
I never figured it out but needed to get something working so I kludged one out of a QAbstractVideoSurface, and it's been working well so far. It's a bit simpler than some of the other implementations in this post, and it's all in one file.
Note that Qt 5.15 or higher is required if you intend to both process frames and play them back with this, since the multi-surface QMediaPlayer::setVideoOutput wasn't added until 5.15. If all you want to do is process video you can still use the code below as a template for pre-5.15, just gut the formatSource_ parts.
Code:
VideoProbeSurface.h (the only file; link is to Gist)
#ifndef VIDEOPROBESURFACE_H
#define VIDEOPROBESURFACE_H
#include <QAbstractVideoSurface>
#include <QVideoSurfaceFormat>
class VideoProbeSurface : public QAbstractVideoSurface {
Q_OBJECT
public:
VideoProbeSurface (QObject *parent = nullptr)
: QAbstractVideoSurface(parent)
, formatSource_(nullptr)
{
}
void setFormatSource (QAbstractVideoSurface *source) {
formatSource_ = source;
}
QList<QVideoFrame::PixelFormat> supportedPixelFormats (QAbstractVideoBuffer::HandleType type) const override {
return formatSource_ ? formatSource_->supportedPixelFormats(type)
: QList<QVideoFrame::PixelFormat>();
}
QVideoSurfaceFormat nearestFormat (const QVideoSurfaceFormat &format) const override {
return formatSource_ ? formatSource_->nearestFormat(format)
: QAbstractVideoSurface::nearestFormat(format);
}
bool present (const QVideoFrame &frame) override {
emit videoFrameProbed(frame);
return true;
}
signals:
void videoFrameProbed (const QVideoFrame &frame);
private:
QAbstractVideoSurface *formatSource_;
};
#endif // VIDEOPROBESURFACE_H
I went for the quickest-to-write implementation possible so it just forwards supported pixel formats from another surface (my intent was to both probe and play back to a QVideoWidget) and you get whatever format you get. I just needed to grab subimages into QImages though, which handles most common formats. But you could modify this to force any formats you want (e.g. you might want to just return formats supported by QImage or filter out source formats not supported by QImage), etc.).
Example set up:
QMediaPlayer *player = ...;
QVideoWidget *widget = ...;
// forward surface formats provided by the video widget:
VideoProbeSurface *probe = new VideoProbeSurface(...);
probe->setFormatSource(widget->videoSurface());
// same signal signature as QVideoProbe's signal:
connect(probe, &VideoProbeSurface::videoFrameProbed, ...);
// the key move is to render to both the widget (for viewing)
// and probe (for processing). fortunately, QMediaPlayer can
// take a list:
player->setVideoOutput({ widget->videoSurface(), probe });
Notes
The only really sketchy thing I had to do was const_cast the QVideoFrame on the receiver side (for read-only access), since QVideoFrame::map() isn't const:
if (const_cast<QVideoFrame&>(frame).map(QAbstractVideoBuffer::ReadOnly)) {
...;
const_cast<QVideoFrame&>(frame).unmap();
}
But the real QVideoProbe would make you do the same thing so, I don't know what's up with that -- it's a strange API. I ran some tests with sw, native hw, and copy-back hw renderers and decoders and map/unmap in read mode seem to be functioning OK, so, whatever.
Performance-wise, the video will bog down if you spend too much time in the callback, so design accordingly. However, I didn't test QueuedConnection, so I don't know if that'd still have the issue (although the fact that the signal parameter is a reference would make me wary of trying it, as well as conceivable issues with the GPU releasing the memory before the slot ends up being called). I don't know how QVideoProbe behaves in this regard, either. I do know that, at least on my machine, I can pack and queue Full HD (1920 x 1080) resolution QImages to a thread pool for processing without slowing down the video.
You probably also want to implement some sort of auto-unmapper utility object for exception safe unmap(), etc. But again, that's not unique to this, same thing you'd have to do with QVideoProbe.
So hopefully that helps somebody else.
Example QImage Use
PS, example of packing arbitrarily-formatted QVideoFrames into a QImage in:
void MyVideoProcessor::onFrameProbed(const QVideoFrame &frame) {
if (const_cast<QVideoFrame&>(frame).map(QAbstractVideoBuffer::ReadOnly)) {
auto imageFormat = QVideoFrame::imageFormatFromPixelFormat(frame.pixelFormat());
QImage image(frame.bits(), frame.width(), frame.height(), frame.bytesPerLine(), imageFormat);
// *if* you want to use this elsewhere you must force detach:
image = image.copy();
// but if you don't need to use it past unmap(), you can just
// use the original image instead of a copy.
// <---- now do whatever with the image, e.g. save() it.
// if you *haven't* copied the image, then, before unmapping,
// kill any internal data pointers just to be safe:
image = QImage();
const_cast<QVideoFrame&>(frame).unmap();
}
}
Notes about that:
Constructing a QImage directly from the data is fast and essentially free: no copies are done.
The data buffers are only technically valid between map and unmap, so if you intend to use the QImage outside of that scope, you'll want to use copy() (or anything else that forces a detach) to force a deep copy.
You also probably want to ensure that the original not-copied QImage is destructed before calling unmap. It's unlikely to cause problems but it's always a good idea to minimize how many invalid pointers are hanging around at any given time, and also the QImage docs say "The buffer must remain valid throughout the life of the QImage and all copies that have not been modified or otherwise detached from the original buffer". Best to be strict about it.

program crashes with haarcascade_fullbody.xml

I am working with OpenCV for Qt.
I am working on doing a program which is able to detect several objects. So far I could make a face, eye and nose detector, but when I try to make a full body detection I get either totally wrong detections, no detections at all or the program crashes. For detecting the full body I just use the same code as for the other detections but with the haarcascade_fullbody.xml file. Is it not possible to use the same code? Why does it work for the other features and not for the full body?
I have also tried to implement a car detection using OpenCV's pretrained models from https://github.com/Itseez/opencv_extra/tree/master/testdata/cv/latentsvmdetector/models_VOC2007 but I get parsing errors.
Thanks in advance!
Code from MainWindow:
void MainWindow::on_btnFullBody_clicked()
{
WriteInLog("Full body detection requested");
QString xml = tr("%1/%2").arg(QApplication::applicationDirPath()).arg(FULL_BODY_FILE);
FeatureDetector detector(xml);
std::vector<QRect> rest;
float scaleFactor= 1.1f;
uint neighbours= 2;
bool ret = detector.DetectFeature(&mSelectedImage, rest, scaleFactor, neighbours);
if (!ret)
{
WriteInLog("No full body has been detected");
}
else
{
QVector<QRect> qRect = QVector<QRect>::fromStdVector(rest);
processedImage(qRect);
WriteInLog("Bodys detected: "+QString::number(qRect.size()));
}
}
Code from DetectFeature:
bool FeatureDetector::DetectFeature(QImage* image, std::vector<QRect> &returnList, float scaleFactor, uint neighbours)
{
returnList.clear();
bool ok = false;
qDebug() << "Starting...";
if (!image->isNull()) {
//Changing from QImage to matrix
QImage temp = image->copy();
cv::Mat res(temp.height(),temp.width(),CV_8UC3,(uchar*)temp.bits(),temp.bytesPerLine());
cv::Mat res_gray;
//Changing the image to grey scale an equalizing the result
cvtColor(res, res_gray,CV_BGR2GRAY);
cv::equalizeHist(res_gray,res_gray);
cv::CascadeClassifier detector;
std::vector< cv::Rect > featureVec;
bool retDetector=true; // detector.load("C:/Users/ansurbcn_2/Pictures/cara.jpg");
qDebug()<<mXmlFilePath;
if (!detector.load(mXmlFilePath.toLatin1().constData()))
{
qDebug() << "Error loading detector";
return false;
}
detector.detectMultiScale(res_gray, featureVec);
//detector.detectMultiScale(res_gray, featureVec, scaleFactor, neighbours, 18|9);
if (retDetector) {
qDebug() << "OK Detector";
}
else {
qDebug() << "Failed Detector";
}
for(size_t i=0; i<featureVec.size();i++)
{
cv::Rect oneFeature =featureVec[i];
QRect qrect(oneFeature.x, oneFeature.y, oneFeature.width, oneFeature.height);
returnList.push_back(qrect);
ok = true;
}
}
return ok;
}

Video not playing continuously

I am playing video in Qt using opencv. I am having 6 tiled view cameras from which I am playing video. The problem is if one of the videos is not playing i.e finishes then the GUI freezes and exits. The error I get is you must reimplement QApplication::notify() and catch the exceptions there. How to do this?
The code I am using is as follows.
Somewhere in a function
void MainWindow::ActivateWindow()
{
//Some part of code to set Index for stacked widget
if(stackWidget->currentIndex()==9)
{
const int imagePeriod == 1000/25;
imageTimer->setInterval(imagePeriod);
connect(imageTimer,SIGNAL(timeout()),this,SLOT(demoSlot());
imageTimer->start();
}
}
In slot demoSlot
void MainWindow::demoSlot()
{
captureCamera1 cvCaptureFromFile("/root/mp.mp4");
captureCamera2 cvCaptureFromFile("/root/mp.mp4");
captureCamera3 cvCaptureFromFile("/root/mp.mp4");
while(imageTimer->isActive())
{
frameCamera1 = cvQueryFrame(captureCamera1);
frameCamera2 = cvQueryFrame(captureCamera2);
frameCamera3 = cvQueryFrame(captureCamera2);
sourceImageCam1 = frameCamera1;
sourceImageCam2 = frameCamera2;
sourceImageCam3 = frameCamera3;
cv::resize(sourceImageCam1,sourceImageCam1,cv::size(400,100),0,0);
cv::resize(sourceImageCam1,sourceImageCam1,cv::size(400,100),0,0);
cv::resize(sourceImageCam1,sourceImageCam1,cv::size(400,100),0,0);
cv::cvtColor(sourceImageCam1,sourceImageCam2,CV_BGR2RGB);
cv::cvtColor(sourceImageCam2,sourceImageCam2,CV_BGR2RGB);
cv::cvtColor(sourceImageCam2,sourceImageCam2,CV_BGR2RGB);
QImage tempImage1 = QImage((const unsigned char* sourceImageCam1.data,sourceImageCam1.cols,sourceImageCam2.rows,QImage::Format_RG888);
QImage tempImage2 = QImage((const unsigned char* sourceImageCam2.data,sourceImageCam2.cols,sourceImageCam2.rows,QImage::Format_RG888);
QImage tempImage3 = QImage((const unsigned char* sourceImageCam3.data,sourceImageCam3.cols,sourceImageCam3.rows,QImage::Format_RG888);
labelCameraCapture1->setPixmap(QPixmap::fromImage(tempImage1)); //label to display video
labelCameraCapture2->setPixmap(QPixmap::fromImage(tempImage2));
labelCameraCapture3->setPixmap(QPixmap::fromImage(tempImage3));
lblCameraCapture1->resize(lblCameraCapture1->Pixmap->size());
lblCameraCapture1->resize(lblCameraCapture1->Pixmap->size());
lblCameraCapture1->resize(lblCameraCapture1->Pixmap->size());
cvWaitkey(20);
qApp->processEvents();
}
if(imageTimer->isActive())
{
imageTimer->stop();
}
else
{
imageTimer->start();
}
}
In header file
cvCapture *captureCamera1;
cvCapture *captureCamera1;
cvCapture *captureCamera1;
IplImage frameCamera1;
IplImage frameCamera2;
IplImage frameCamera3;
cv::Mat sourceImageCam1;
cv::Mat sourceImageCam2;
cv::Mat sourceImageCam3;
This will do the trick changing that to 3 movies is simple.
class MainWindow : public QMainWindow {
Q_OBJECT
explicit QMainWindow(QWidget *parent) ....
// prepare timer and so on
public slots:
void startVideo() {
vid1.close();
vid1.open("/root/mp.mp4");
imageTimer->start();
}
void demoSlot() {
cv::Mat frame;
vid1 >> frame;
cv::cvtColor(frame,frame,CV_BGR2RGB);
QImage img((uchar*) frame.data, frame.cols, frame.rows, frame.step, QImage::Format_RGB888);
label1->setPixmap(QPixmap::fromImage(img));
}
private:
...
QTimer *imageTimer;
cv::VideoCapture vid1;
};
Check if frame captured from camera is NULL. Then simply skip processing steps for this camera.
And it'll be better to not mix C++ and C interfaces (I mean cv::Mat and IplImage).

Make buffer of cv mat Point to the buffer of QImage

I would like to make the buffer of cv::mat point to the buffer of QImage but not copy
the data of QImage into the cv::mat.
cv::Mat const reference_qimage_to_mat(QImage const &img, int format)
{
cv::Mat mat(img.height(), img.width(), format);
for(int i = 0; i != mat.rows; ++i)
{
//pseudo code, wouldn't work
//mat.ptr(i) = img.scanLine(i);
}
return mat;
}
I try to search the answer by google but I could only find how to copy
the data of QImage into cv::mat.Thanks
the cv::Mat object is simply a header for the image data, so you can do it upon your object construction:
cv::Mat mat(img.height(), img.width(), type, img.bits());
where type depends on your data, CV_8UC1 for single channel, CV_8UC3 for RGB, etc.

Qt 4.8 - QFileIconProvider, Getting icon for non-existent file (based on extension)

I'm currently trying to get the icon based on a file extension, but it seems like QFileIconProvider will only return an icon if it can actually read an existing file. Is there any way I can get a QIcon based off of a file extension? One alternative would be to write a temporary file with the desired extension, but that is very inefficient so I'm looking for a way around.
Any help would be appreciated!
Here's my solution for Windows:
iconprovider.h:
class IconProvider
{
public:
static IconProvider * instance();
static QIcon fileIcon(const QString &filename);
static QIcon dirIcon();
private:
IconProvider() {}
private:
static IconProvider *self;
QPixmapCache iconCache;
QFileIconProvider iconProvider;
};
iconprovider.cpp:
IconProvider *IconProvider::self = 0;
IconProvider *IconProvider::instance()
{
if(!self)
self = new IconProvider();
return self;
}
QIcon IconProvider::fileIcon(const QString &filename)
{
QFileInfo fileInfo(filename);
QPixmap pixmap;
#ifdef Q_OS_WIN32
if (fileInfo.suffix().isEmpty() || fileInfo.suffix() == "exe" && fileInfo.exists())
{
return instance()->iconProvider.icon(fileInfo);
}
if (!instance()->iconCache.find(fileInfo.suffix(), &pixmap))
{
// Support for nonexistent file type icons, will reimplement it as custom icon provider later
/* We don't use the variable, but by storing it statically, we
* ensure CoInitialize is only called once. */
static HRESULT comInit = CoInitialize(NULL);
Q_UNUSED(comInit);
SHFILEINFO shFileInfo;
unsigned long val = 0;
val = SHGetFileInfo((const wchar_t *)("foo." + fileInfo.suffix()).utf16(), 0, &shFileInfo,
sizeof(SHFILEINFO), SHGFI_ICON | SHGFI_USEFILEATTRIBUTES);
// Even if GetFileInfo returns a valid result, hIcon can be empty in some cases
if (val && shFileInfo.hIcon)
{
pixmap = QPixmap::fromWinHICON(shFileInfo.hIcon);
if (!pixmap.isNull())
{
instance()->iconCache.insert(fileInfo.suffix(), pixmap);
}
DestroyIcon(shFileInfo.hIcon);
}
else
{
// TODO: Return default icon if nothing else found
}
}
#else
// Default icon for Linux and Mac OS X for now
return instance()->iconProvider.icon(fileInfo);
#endif
return QIcon(pixmap);
}
QIcon IconProvider::dirIcon()
{
return instance()->iconProvider.icon(QFileIconProvider::Folder);
}

Resources