Save QML image inside c++ - c++

I am trying to display network image using qml and then save this image using c++ code,
Here is the qml code,
import QtQuick 2.3
import QtQuick.Window 2.2
import com.login 1.0
Window {
visible: true
width : 500
height: 500
Login{id: login}
MouseArea {
anchors.fill: parent
onClicked: {
// Qt.quit();
login.save(image);
}
}
Image {
id: image
source: "http://www.test.com/webp/gallery/4.jpg"
}
}
And inside my login class saving image like,
void Login::save( QQuickItem *item)
{
qDebug()<<"width: "<<item->width();
qDebug()<<"height: "<<item->height();
QQuickWindow *window = item->window();
QImage image = window->grabWindow();
QPixmap pix = QPixmap::fromImage(image);
pix.save("C:/Users/haris/Desktop/output.png");
}
I am getting the correct width and height of the image inside c++ class, but the problem is I cannot find a way to save the image item from QQuickItem.
Right now I am saving the image by grabing the window, which actually not giving the actual image size on output file, instead giving output file with current qml window size.
Basically I am following the code here saving QML image but it seems QDeclarativeItem is deprecated in Qt5, so I choose QQuickItem where as there is no paint option in QQuickItem.

Fortunately QQuickItem has a convenient grabToImage function which does that.
void Login::save( QQuickItem *item)
{
QSharedPointer<const QQuickItemGrabResult> grabResult = item->grabToImage();
connect(grabResult.data(), &QQuickItemGrabResult::ready, [=]() {
grabResult->saveToFile("C:/Users/haris/Desktop/output.png");
//grabResult->image() gives the QImage associated if you want to use it directly in the program
});
}
Alternate solution without using lambdas:
void Login::save( QQuickItem *item)
{
QSharedPointer<const QQuickItemGrabResult> grabResult = item->grabToImage();
/* Need to store grabResult somewhere persistent to avoid the SharedPointer mechanism from deleting it */
...
connect(grabResult.data(), SIGNAL(ready()), this, SLOT(onAsynchroneousImageLoaded()));
}
void Login::onAsynchroneousImageLoaded() {
auto grabResult = qobject_cast<const QQuickItemGrabResult*>(sender());
if (grabResult) {
grabResult->saveToFile("C:/Users/haris/Desktop/output.png");
} else {
//something went wrong
}
});

In a QObject-derived class (ImageSaver) register it as you would. It needs one member:
bool ImageSaver::saveImage(const QUrl &imageProviderUrl, const QString &filename){
qDebug() << Q_FUNC_INFO <<imageProviderUrl << filename;
QQmlEngine *engine = QQmlEngine::contextForObject(this)->engine();
QQmlImageProviderBase *imageProviderBase = engine->imageProvider(imageProviderUrl.host());
QQuickImageProvider *imageProvider = static_cast<QQuickImageProvider*>(imageProviderBase);
QSize imageActualSize;
QSize imageRequestedSize;
QString imageId = imageProviderUrl.path().remove(0,1);
QImage image = imageProvider->requestImage(imageId, &imageActualSize, imageRequestedSize);
qDebug() << Q_FUNC_INFO << imageId << imageActualSize;
return image.save(filename);
}
then in QML:
ImageSaver { id: imageSaver}
...
imageSaver.saveImage(image.source, "my.png");
...
Whereas grabToImage will grab the item using the items's size, this can preserve the actual size of the image.

Related

How load QImage from path?

I captured an Image with camera in my qt quick application.I want to send the path to my c++ code and load that Image in c++ QImage.But the Path is image://camera/preview_1 and I don't know How work with that path?
Camera {
id: camera
imageCapture {
onImageCaptured: {
console.log("Preview = "+preview);
photoPreview.source = preview
console.log("CapturedImagePath => "+camera.imageCapture.capturedImagePath);
up.loadImage(preview);
}
}
c++ class
void UserProfile::loadImage(QString path)
{
QUrl imageUrl(path);
qWarning()<<"imageUrl.host()=>"<<imageUrl.host();
qWarning()<<"imageUrl.path()=>"<<imageUrl.path();
qWarning()<<"imageUrl.toLocalFile()=>"<<imageUrl.toLocalFile();
bool isOpend= m_image.load(path); //m_image is an QImage object
qWarning()<<"Image loaded=> "<<isOpend;
}
Application output
D MyApp: qml: Preview = image://camera/preview_1
D MyApp: qml: CapturedImagePath =>
W MyApp: imageUrl.host()=> "camera"
W MyApp: imageUrl.path()=> "/preview_1"
W MyApp: imageUrl.toLocalFile()=> ""
W MyApp: Image loaded=> false
The URL image://camera/preview_1 means that the image data is living in a QQuickImageProvider instance. Probably, it's a QQuickImageProvider instance created by Camera.
As the UserProfile instance is living in the same QQmlEngine as the camera lives, you can
void UserProfile::loadImage(const QString &path)
{
auto myQmlEngine = qmlEngine(this);
if(myQmlEngine==nullptr)
return;
QUrl imageUrl(path);
auto provider = reinterpret_cast<QQuickImageProvider*>( myQmlEngine->imageProvider(imageUrl.host()));
if (provider->imageType()==QQuickImageProvider::Image){
QImage img = provider->requestImage(imageUrl.path().remove(0,1),nullptr,QSize());
// do whatever you want with the image
}
}
Be careful with the reinterpret_cast. You also have to make sure that the QQuickImageProvider::imageType() is returning QQmlImageProviderBase::Image.
and you could also use capturedImagePath instead of the preview URL to prevent this complexity if it could be an option for your usecase.

Qt showing live image, escape from massive signals

I have a QThread running, trying to decode image from a camera:
struct ImageQueue
{
enum {NumItems = 5};
tbb::concurrent_bounded_queue<DecodedImage> camera_queue_; // decoded image
tbb::concurrent_bounded_queue<DecodedImage> display_queue_; // widget display image
ImageQueue(int camid){camera_queue_.set_capacity(NumItems);display_queue_.set_capacity(NumItems)}
};
std::shared_ptr<ImageQueue> camera_queue;
void Worker::process()
{
while(1)
{
if(quit_)
break;
DecodedImage tmp_img;
camera_queue->camera_queue_.pop(tmp_img);
camera_queue->display_queue_.push(tmp_img);
emit imageReady();
}
emit finished();
}
And this thread is part of the Camera Class:
void Camera::Start()
{
work_ = new Worker();
work_->moveToThread(workerThread_);
QObject::connect(workerThread_, &QThread::finished, work_, &QObject::deleteLater);
QObject::connect(this, &Camera::operate, work_, &Worker::process);
QObject::connect(this, &Camera::stopDecode, work_, &Worker::stop);
QObject::connect(work_, &Worker::imageReady, this, &Camera::DisplayImage);
workerThread_->start();
emit operate();
}
On the widget display side:
class CVImageWidget : public QGraphicsView
{
Q_OBJECT
public:
void display(DecodedImage& tmp_img);
~CVImageWidget();
private:
QGraphicsScene *scene_;
};
CVImageWidget widget;
void Camera::DisplayImage()
{
if(camera_queue != nullptr)
{
DecodedImage tmp_img;
camera_queue->display_queue_.pop(tmp_img);
widget->display(tmp_img);
}
}
void CVImageWidget::display(DecodedImage& tmp_img)
{
if(!tmp_img.isNull())
{
scene_->clear();
scene_->addPixmap(QPixmap::fromImage(tmp_img));
}
}
My question is:
Is there a way to save me from the massive imageReady signals ? I use this signal to display image because the image has to be shown in main thread otherwise the image will not be displayed. But the massive amount of signals will make the GUI response become slower.
Is there a way to fix that ? Thanks.
You do not want to be altering a graphics scene each time an image arrives. You can just as well use a QLabel and its setPixmap method, or a simple custom widget.
There is no need for a display queue: you're only interested in the most recent frame. If the UI thread lags behind the camera thread, you want to drop obsolete frames.
You have to rethink if the camera_queue needs to be a concurrent queue, since you access it from a single thread only, and whether you need that queue at all.
The typical image producer-consumer would work as follows: a Camera class interfaces with a camera and emits a hasFrame(const DecodedImage &) signal each time it got a new frame. No need for a queue: any listening object thread's event queues are concurrent queues already.
A display widget simply accepts the images to display:
class Display : public QWidget {
Q_OBJECT
DecodedImage m_image;
protected:
void paintEvent(QPaintEvent *event) {
QPainter painter{this};
painter.drawImage(0, 0, m_image);
}
public:
Display(QWidget *parent = nullptr) : QWidget{parent} {}
QSize sizeHint() const override { return m_image.size(); }
Q_SLOT void setImage(const QImage & image) {
auto oldSize = m_image.size();
m_image = image;
if (oldSize != m_image.size())
updateGeometry();
update();
}
};
You then connect the image source to the display widget and you're done. The paintEvent or other GUI thread operations can take as long as you wish, even if the camera produces images much faster than the display can consume them, you'll still show the most recent image each time the widget gets a chance to repaint itself. See this answer for a demonstration of that fact. See this answer for a similar demonstration that works on a fixed-pipeline OpenGL backend and uses the GPU to scale the image.
Mutex approach should be faster than emiting Qt signals. I've once tried to skip Qt signals by using STL condition_variable and it worked just fine for me.

How to repaint QWidget encapsulated in QDeclarativeItem in QML?

I work in a C++/QML environment and I use Qt 4.8 with QtQuick 1.0.
I have a QWidget derivated class, QCustomPlot, and I encapsulated it in a custom QDeclarativeItem derived class. I use a QGraphicsProxyWidget to embed the QWidget, and it appears nicely upon creation. I would like to update the chart periodically, but I simply cannot, no matter what I do it stays however I initiated it in the constructor.
Here is the code (somewhat simplified) I have:
flowgrafik.h:
class FlowGrafik : public QDeclarativeItem
{
Q_OBJECT
public:
explicit FlowGrafik(QDeclarativeItem *parent = 0);
~FlowGrafik();
void addFlow(double flow);
signals:
public slots:
private:
QCustomPlot * customPlot;
QGraphicsProxyWidget * proxy;
QVector<double> x, y;
};
flowgrafik.cpp:
FlowGrafik::FlowGrafik(QDeclarativeItem *parent) : QDeclarativeItem(parent)
{
customPlot = new QCustomPlot();
proxy = new QGraphicsProxyWidget(this);
proxy->setWidget(customPlot);
this->setFlag(QGraphicsItem::ItemHasNoContents, false);
customPlot->setGeometry(0,0,200,200);
/* WHAT I WRITE HERE WILL BE DISPLAYED */
// pass data points to graph:
customPlot->graph(0)->setData(x, y);
customPlot->replot();
}
FlowGrafik::~FlowGrafik()
{
delete customPlot;
}
void FlowGrafik::addFlow(double flow)
{
//THIS PART DOES NOT GET DISPLAYED
for (int i=0; i<99; ++i)
{
y[i] = y[i+1];
}
y[99] = flow;
customPlot->graph(0)->setData(x, y);
customPlot->replot();
this->update();
}
mainview.qml:
Rectangle {
id: flowGrafik
objectName: "flowGrafik"
x: 400
y: 40
width: 200
height: 200
radius: 10
FlowGrafik {
id: flowGrafikItem
}
}
I would really appreciate if anyone could tell me why my QCustomPlot QWidget does not replot.
Eventually the solution was to create a pointer in the C++ code that points at the QML item.
I accidentally created an other instance in the C++, modified that one, and expected the QML instance to change.
QDeclarativeView mainView;
mainView.setSource(QUrl("qrc:///qml/qml/mainview.qml"));
Flowgrafik * flowGrafik = mainView.rootObject()->findChild<QObject*>(QString("flowGrafikItem"));
//or if Flowgrafik was the main element: Flowgrafik * flowGrafik = mainView.rootObject();
flowGrafik->addFlow(x);

QWidget::grab function for videos: cannot catch

I want to save the current window to an image (like as ATL+PrintScreen) in Qt. Example, for the Qt Media Player Example (you can get all the code in Qt Creator examples when search "player", I added this code to player.h :
//private slots:
...
void exportVideoToImages();
and in player.cpp:
void Player::exportVideoToImages()
{
qDebug() << "ok";
QPixmap const& px = grab();
px.save("File.png");
}
Add this line to the the constructor to trigger the slot:
new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_G), this, SLOT(exportVideoToImages()));
So, when I trigger CTRL+G, I will receive an image "File.png". It worked, but the problem is the playing video cannot be catch.
This is the two images, one from Alt+PrintScreen and one from the program:
Why is it? How can I grab the video in Qt? Can you show me?
Thank you very much!
I found the solution:
QScreen *screen = QGuiApplication::primaryScreen();
if (screen) {
QPixmap px =screen->grabWindow(this->winId());
px.save("screen.png");
}

Can't visibly instantiate at runtime via QQmlComponent

I am kidding around with a quite simple QML sample that should end up being some kind of chessboard, but for some reason I can't properly add cells at runtime. A cell is defined using a C++ Class (BasicCell which extends QQuickItem) and can be styled using Qml (cell.qml):
BasicCell {
width: 32
height: 32
Rectangle {
anchors.fill : parent
color : "green"
}
}
I use QQmlComponent to construct instances of this "styled" BasicCell at runtime:
QQmlComponent cellComponent(qmlEngine(), cellUrl, this);
// Make sure we could actually load that QML component
if (cellComponent.status() != QQmlComponent::Ready)
{
std::cerr << "Error loading cell.qml:" << std::endl;
for (const auto& err : cellComponent.errors())
{
std::cerr << " " << err.toString().toStdString() << std::endl;
}
}
for (int x = 0; x < mNumTiles.width(); ++x)
{
for (int y = 0; y < mNumTiles.height(); y++)
{
BasicCell* cell = qobject_cast<BasicCell*>(cellComponent.create());
cell->setParent(this);
cell->setSize(QSize(tileSize(), tileSize()));
cell->setGridPos(QPoint(x, y));
childItems().append(cell);
mCells.insert(cell->gridPos(), cell);
}
}
When using the QML Debugger I can see that I have ended up with the "correct" hierarchy:
Game
BasicCell
Rectangle
BasicCell
Rectangle
...
But I can't see a thing ... I double and triple checked: All of those rectangles and basic cells do have the appropriate sizes set.
Getting more and more frustrated, I finally copied the code from the cell.qml and pasted it as a direct child into the Board.qml. To my astonishment, this renders the cell exactly as I would have expected it.
What am I missing in my use of QQmlComponent that differs from this kind of instantionation in QML?
Game
{
// Should be created at runtime using QQmlComponent
BasicCell {
width: 32
height: 32
Rectangle {
anchors.fill: parent
color : "green"
}
gridPos: "0,0"
}
}
cell->setParent(this);
should be
cell->setParentItem(this);
The concept of the visual parent differs from that of the QObject
parent. An item's visual parent may not necessarily be the same as its
object parent. See Concepts - Visual Parent in Qt Quick for more
details.
That was taken from:
http://qt-project.org/doc/qt-5/qquickitem.html#parent-prop