How to set QPixmap in QQuickImageProvider class from another class? - c++

I am developing an application using QML and C++, the goal is to download an image named "test.jpg" from a HTTP server and show it in a QML Image component without saving it in hard disk drive.
The class "AppController" downloads the image "test.jpg" from server and store in a global QPixmap variable named "imageReadFromServer".
#ifndef APPCONTROLLER_H
#define APPCONTROLLER_H
#include <QObject>
class AppController : public QObject
{
Q_OBJECT
public:
AppController(QObject *parent = 0);
Q_INVOKABLE void sendImage(QString _imagePath, QString serverLocation);
Q_INVOKABLE void readImage(QString serverLocation);
private:
QByteArray serverResponse;
QPixmap imageReadFromServer;
};
#endif // APPCONTROLLER_H
To display images on QML Image component I know that it is necessary to use "QQuickImageProvider" class, I have done one class named "ImageProvider" and reimplemented its virtual method QPixmap requestPixmap() to handle when QML requests image showing. To test if everything was working well until now, I used the next example code provided by Qt documentation that returns a QPixmap filled with blue color:
QPixmap ImageProvider::requestPixmap(const QString &id, QSize *size, const QSize &requestedSize)
{
qDebug() << "pixmap requested";
int width = 160;
int height = 100;
if(size)
*size = QSize(width,height);
QPixmap pixmap(requestedSize.width() > 0 ? requestedSize.width() : width,
requestedSize.height() > 0 ? requestedSize.height() : height);
pixmap.fill(QColor("blue").rgba());
//return pixmap;
return pixmap;
}
In main.cpp I added its respective image provider.
engine.addImageProvider(QLatin1String("imagefromserver"), new ImageProvider);
When download button is clicked on QML, it calls ImageProvider class method successfully:
onClicked: {
imageVisualizer.visible = true
imageVisualizer.source = "image://imagefromserver/"
}
What I need is:
When requestPixmap() method from ImageProvider class is called, it should return the global variable imageReadFromServer already stored in AppController class.
I have tried to make AppController class inherits directly from QQuickImageProvider but it didn't work because imageReaderFromServer value was weirdly cleared just before call requestPixmap().
I thought that I could use SIGNALS and SLOTS to pass QPixmap between classes but QQuickImageProvider doesn't inherits from QObject, so I cannot use them.
I appreciate any suggestion.
Thanks.
EDIT
I have again tried to implement QQuickImageProvider inheritance in AppController class. It is already working, the mistake was in main.cpp file, I was declaring image provider as shown below:
AppController applicationController;
QQmlApplicationEngine engine;
engine.addImageProvider(QLatin1String("imagefromserver"), new AppController);
This created a new instance of AppController class, and when I accessed to requestPixmap() function it couldn't get the global QPixmap variable imageReadFromServer because it was created in the instance named applicationController. The solution was to assign the same instance to imageProvider as shown below:
engine.addImageProvider(QLatin1String("imagefromserver"), &applicationController);

Related

Qt update an image in the UI through callback

I have a function streamCamera(*callback) where the callback is void callback(Image) which can be used to preview the stream in any UI framework we want.
I was able to stream with this function in a opencv imshow window
void callback(image)
{
cv::imshow("image", image);
waitKey(1);
}
while the imshow is a static function and was able to stream.
Is there anything similar i can do with Qt?
What I tried,
I have a class MainWindow which inherits QMainWindow, inside the class i have attached a QGraphicsPixmapItem pixImage into the ui with ui->graphicsView->scene()->addItem(&pixImage);
since the pixImage is not accessible to the callback function, cause the Mainwindow object is not global. I was not able to set image in the pixmap;
So i ended up doing a hack, just created a global pointer a QGraphicsPixmapItem *pixImagePtr and attached the pixImage before starting the streamCamera(*callback) and inside the callback i use the pixImagePtr to set the image GUI, and it worked.
But this hack seems like not the right way to do, so it would be helpful if some qt experts shed some light on it.
Sounds like something that should be done with signals.
Let's say you have your MainWindow and Worker class.
Worker wants to show some image inside the MainWindow, you can do it like this.
class MainWindow : public QMainWindow {
Q_OBJECT
// Other members
public slots:
void showImage(const QPixmap &pixmap) {
pixImage.setPixmap(pixmap);
}
};
class Worker : public QObject {
Q_OBJECT
public:
Worker(MainWindow* parent) : QObject{parent} {
connect(this, &Worker::pixmapChanged,
parent, &MainWindow::showImage);
}
signals:
void pixmapChanged(QPixmap pixmap);
}
From now inside Worker you should be able to use pixmapChanged signal to change MainWindow image like so.
const QPixmap pixmap = getNewPixmap();
emit pixmapChanged(pixmap);

Resizing Qlabel image upon resizing window using resizeEvent

I want to use ResizeEvent to receive the size of the window and set it to the size of a QLabel, in order to make the image stretched and adapted to the window's dimensions.by a left click of the mouse I can resize my window and the image takes a new size.
You must have the following considerations:
It is not necessary to store QPixmap by means of a pointer since when passing it to the QLabel it is copied by value.
Therefore, if you change the size of the QPixmap p will not be reflected in the QLabel since the QPixmap that has the QLabel is a copy of the one you established at the beginning.
It is not necessary to use a layout for this task since it will create an infinite loop since this also intervenes in the resizeEvent of the widget where it has been established so if you change the size of the QLabel, it will change the size of the QWidget, and this again I will try to change the QLabel, and so on.
It is not advisable to modify the original QPixmap since changing its size modifies the pixels and you will get an unexpected effect.
Using the above we obtain the following code:
*.h
#ifndef TESTSIZE_H
#define TESTSIZE_H
#include <QWidget>
class QLabel;
class testsize : public QWidget
{
Q_OBJECT
public:
explicit testsize(QWidget *parent = 0);
~testsize();
private:
QLabel *image;
QPixmap original_px;
protected:
void resizeEvent(QResizeEvent *event);
};
#endif // TESTSIZE_H
*.cpp
#include "testsize.h"
#include <QLabel>
#include <QResizeEvent>
testsize::testsize(QWidget *parent) :
QWidget(parent)
{
image = new QLabel(this);
original_px = QPixmap(":/wallpaper.jpg");
image->setPixmap(original_px);
resize(640, 480);
}
testsize::~testsize()
{
}
void testsize::resizeEvent(QResizeEvent *event)
{
QPixmap px = original_px.scaled(event->size());
image->setPixmap(px);
image->resize(event->size());
QWidget::resizeEvent(event);
}
You can find the complete example in the following link.

Using the QGraphicsView Method Scene() to Assign a QGraphicsScene Variable

What would the syntax look like if I wanted to even use QGraphicsView::scene() in the first place? My goal is to change the scale of a pixmap in my graphics view object with sliders. I want to use QgraphicsView::scene() and QScene::itemsAt() etc. to find the original pixmap and then use QPixmap::scaled() (this I found would be the only way to ensure my cosmetic property set for the pixmap holds true). However I am having issues with the syntax of QGraphicsView::scene(). My attempt is below. I am also creating a Qt widget application for context.
QGraphicsViewScene graphicsScene = ui->PixmapView->scene();
QGraphicsPixmapItem graphicsPixmapItem = graphicsScene.itemAt(0, 0);
edit
If I was to store my QPixmap pixmap* as a member variable I am not entirely sure how to implement that where it remains in scope for my slots.
edit
static member variables?
You can make your QGraphicsPixmapItem object a member variable of your class. Then you would be able to access it from any of your class member functions.
Here is a quick example:
class MyClass : public QWidget
{
Q_OBJECT
public:
MyClass(QWidget *parent = nullptr) : QWidget(parent)
{
// create graphics view, scene, etc..
}
public slots:
void openActionTriggered()
{
...
myItem = scene->addPixmap(myPixmap); // you can create your item however you want.. this is just an example
}
void mySlot()
{
if(myItem)
{
// do something with myItem
}
}
private:
QGraphicsPixmapItem *myItem = nullptr; // myItem is a member variable of
QGraphicsScene *scene = nullptr; // I made scene a member variable so it can be accessed from any member functions
}

Creating QIcon from SVG contents in memory

How can we create the QIcon object having SVG icon contents in the memory buffer?
P.S. Initially wanted to create QSvgIconEngine, but it is hidden on the plugins layer, and I can not create it explicitly. How can I do it with loading from plugin (taking in account, that plugin is loaded)?
After digging for a while here and there, and digging in how QIcon itself uses an svg file to load an icon, here's what I learned:
QIcon, when called with an svg file (or any other image type for that matter), it calles addFile() subsequently, which only uses the extension of the file (called QFileInfo::suffix in Qt) to determine the method that converts the image file to an icon.
The method (semantically speaking) is determined by a QIconEngine instance
QIconEngine classes for every image type are not apparently simply accessible by us (the Qt developers); apparently there's a plugin system to be used and it's not available at compile-time (not simply at least)
On the other hand; How does QIcon work? When an icon is requested from QIcon, it uses the information passed to it to determine what engine to use, and creates an instance of the engine. Then, every time the icon needs to draw something, it asks the engine to draw an icon with some size given to it. The size is used on the function QIconEngine::pixmap(), which creates a pixmap with the required size, then the method QIconEngine::paint() is used to paint on that pixmap.
So, given this information, the solution is simply to write an Icon Engine that QIcon will use in order to generate the icon depending on the size passed to it. Here's how to do that:
So here's the header file SvgIconEngine.h
#ifndef SVGICONENGINE_H
#define SVGICONENGINE_H
#include <QIconEngine>
#include <QSvgRenderer>
class SVGIconEngine : public QIconEngine {
QByteArray data;
public:
explicit SVGIconEngine(const std::string &iconBuffer);
void paint(QPainter *painter, const QRect &rect, QIcon::Mode mode,
QIcon::State state) override;
QIconEngine *clone() const override;
QPixmap pixmap(const QSize &size, QIcon::Mode mode,
QIcon::State state) override;
signals:
public slots:
};
#endif // SVGICONENGINE_H
And here's the implementation, SvgIconEngine.cpp
#include "SvgIconEngine.h"
#include <QPainter>
SVGIconEngine::SVGIconEngine(const std::string &iconBuffer) {
data = QByteArray::fromStdString(iconBuffer);
}
void SVGIconEngine::paint(QPainter *painter, const QRect &rect,
QIcon::Mode mode, QIcon::State state) {
QSvgRenderer renderer(data);
renderer.render(painter, rect);
}
QIconEngine *SVGIconEngine::clone() const { return new SVGIconEngine(*this); }
QPixmap SVGIconEngine::pixmap(const QSize &size, QIcon::Mode mode,
QIcon::State state) {
// This function is necessary to create an EMPTY pixmap. It's called always
// before paint()
QImage img(size, QImage::Format_ARGB32);
img.fill(qRgba(0, 0, 0, 0));
QPixmap pix = QPixmap::fromImage(img, Qt::NoFormatConversion);
{
QPainter painter(&pix);
QRect r(QPoint(0.0, 0.0), size);
this->paint(&painter, r, mode, state);
}
return pix;
}
Notice: You must override clone(), because it's an abstract method, and you must override pixmap(), because without that, you'll not have an empty pixmap to paint the svg on.
To use this, simply do this:
std::string iconSvgData = GetTheSvgPlainData();
QIcon theIcon(new SVGIconEngine(iconSvgData));
//Use the icon!
Notice that QIcon takes ownership of the engine object. It'll destroy it when it's destroyed.
Have fun!
I don't have c++ at hand, but it should be easy to convert:
QtGui.QIcon(
QtGui.QPixmap.fromImage(
QtGui.QImage.fromData(
b'<svg version="1.1" viewBox="0 0 32 32"'
b' xmlns="http://www.w3.org/2000/svg">'
b'<circle cx="16" cy="16" r="4.54237"/></svg>')))
This code will create transparent 32 by 32 pixels icon with a black circle.
How can we create the QIcon object having SVG icon contents in the
memory buffer?
For that all the required functionality is delivered via external interface of QSvgRenderer class. To construct that type of renderer we need to use either of:
QSvgRenderer(const QByteArray &contents, QObject *parent = Q_NULLPTR);
QSvgRenderer(QXmlStreamReader *contents, QObject *parent = Q_NULLPTR);
or we can just load the content with:
bool QSvgRenderer::load(const QByteArray &contents)
bool QSvgRenderer::load(QXmlStreamReader *contents)
And to create the actual icon:
QIcon svg2Icon(const QByteArray& svgContent, QPainter::CompositionMode mode =
QPainter::CompositionMode_SourceOver)
{
return QIcon(util::svg2Pixmap(svgContent, QSize(128, 128), mode));
}
QPixmap svg2Pixmap(const QByteArray& svgContent,
const QSize& size,
QPainter::CompositionMode mode)
{
QSvgRenderer rr(svgContent);
QImage image(size.width(), size.height(), QImage::Format_ARGB32);
QPainter painter(&image);
painter.setCompositionMode(mode);
image.fill(Qt::transparent);
rr.render(&painter);
return QPixmap::fromImage(image);
}
We can also use other composition modes as well, say,
QPainter::RasterOp_NotSourceOrDestination to invert the icon color.

QAbstractVideoSurface example

I'm trying to make myself a QML Camera item which has more functions, and also provide a source to the VideoOutput element. Such as this:
VideoOutput{
source:mycamera
}
MyCustomCamera{
id:mycamera
}
in the document it says
If you are extending your own C++ classes to interoperate with
VideoOutput, you can either provide a QObject based class with a
mediaObject property that exposes a QMediaObject derived class that
has a QVideoRendererControl available, or you can provide a QObject
based class with a writable videoSurface property that can accept a
QAbstractVideoSurface based class and can follow the correct protocol
to deliver QVideoFrames to it.
I have tried giving my object a private property mediaObject, which is of type QCamera, but looks like QCamera does not have a QVideoRenderControl (or its my fault not knowing how to do it correctly).
I need to achieve the effect I've shown in the beginning, anyway is welcomed.
Or otherwise can anyone give me a short example on what is meant by "a writable videoSurace property that accept blablabla and follow the correct protocol"?
I can't help you with your main concern but i can give you an example usage of the videoSurface.You can use the "writable videoSurface" like this:
My example consists of three main steps:
You write a class that has a QAbstactVideoSurface property. This class will be your video provider which can display frames on the VideoOutput via calling its present() function.
videoadapter.h
#ifndef VIDEOADAPTER_H
#define VIDEOADAPTER_H
#include <QObject>
#include <QAbstractVideoSurface>
#include <QVideoSurfaceFormat>
#include <QTimer>
class VideoAdapter : public QObject
{
Q_OBJECT
Q_PROPERTY(QAbstractVideoSurface* videoSurface READ videoSurface WRITE setVideoSurface NOTIFY signalVideoSurfaceChanged)
public:
explicit VideoAdapter(QObject *parent = nullptr);
QAbstractVideoSurface *videoSurface() const;
void setVideoSurface(QAbstractVideoSurface *videoSurface);
signals:
void signalVideoSurfaceChanged();
private slots:
void slotTick();
private:
void startSurface();
private:
QAbstractVideoSurface *mVideoSurface;
QVideoSurfaceFormat *mSurfaceFormat;
QImage *mImage;
QTimer mTimer;
};
#endif // VIDEOADAPTER_H
videoadapter.cpp
#include "videoadapter.h"
#include <QDebug>
VideoAdapter::VideoAdapter(QObject *parent)
: QObject(parent), mVideoSurface(nullptr), mSurfaceFormat(nullptr)
{
mTimer.setInterval(1000);
connect(&mTimer, &QTimer::timeout, this, &VideoAdapter::slotTick);
}
QAbstractVideoSurface *VideoAdapter::videoSurface() const
{
return mVideoSurface;
}
void VideoAdapter::setVideoSurface(QAbstractVideoSurface *videoSurface)
{
if(videoSurface != mVideoSurface)
{
mVideoSurface = videoSurface;
emit signalVideoSurfaceChanged();
startSurface();
// This is the test timer that will tick for us to present the image
// on the video surface
mTimer.start();
}
}
void VideoAdapter::slotTick()
{
QVideoFrame frame(*mImage);
mVideoSurface->present(frame);
}
void VideoAdapter::startSurface()
{
mImage = new QImage("../resources/images/test.jpg");
auto pixelFormat = QVideoFrame::pixelFormatFromImageFormat(mImage->format());
mSurfaceFormat = new QVideoSurfaceFormat(mImage->size(), pixelFormat);
if(!mVideoSurface->start(*mSurfaceFormat))
{
qDebug() << "Surface couldn't be started!";
}
}
This class only loads an image file and displays it with the usage of a timer but in your case you will be having a frame source so you can change this to suit your needs. If you can convert your frame to QImage of QVideoFrame you can display it like this.
You have to make this class usable in QML. In my case i created an object and made it visible to QML via setting it as a property.
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
QQmlDebuggingEnabler enabler;
VideoAdapter adapter;
// When you do this this object is made visible to QML context with the
// given name
engine.rootContext()->setContextProperty("videoAdapter", &adapter);
const QUrl url(QStringLiteral("qrc:/main.qml"));
QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
&app, [url](QObject *obj, const QUrl &objUrl) {
if (!obj && url == objUrl)
QCoreApplication::exit(-1);
}, Qt::QueuedConnection);
engine.load(url);
return app.exec();
}
You give this object to the VideoOutput as source in QML.
Window {
visible: true
width: 640
height: 480
color: "black"
title: qsTr("Video Player")
VideoOutput {
id: videoPlayer
anchors.fill: parent
source: videoAdapter
}
}
This example as i said is a simple one that only loads an image and only displays that one image periodically.
This question is an old one and you probably moved on but hope this can at least help other people.
The code provided by #U.Tuken works fine, except if I change the name of property name in Q_PROPERTY from "videoSurface" to any other word, it doesn't work. That is very strange behaviour cause from Qt's point of view "videoSurface" is just a name.
Additionally I got error
"qt.gui.icc: fromIccProfile: failed minimal tag size sanity".
This error pops up if the imported "JPG" is not of correct format
as per this link.
Changing the "JPG" file helped me get rid of the above warning.