Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qquickitemgrabresult.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 Jolla Ltd, author: <gunnar.sletta@jollamobile.com>
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
5#include <private/qtquickglobal_p.h>
7
9#include "qquickwindow.h"
10#include "qquickitem.h"
11#if QT_CONFIG(quick_shadereffect)
12#include "qquickshadereffectsource_p.h"
13#endif
14
15#include <QtQml/QQmlEngine>
16#include <QtQml/QQmlInfo>
17
18#include <private/qquickpixmap_p.h>
19#include <private/qquickitem_p.h>
20#include <private/qsgcontext_p.h>
21#include <private/qsgadaptationlayer_p.h>
22
23#include <QtCore/qmutex.h>
24#include <QtCore/qpointer.h>
25
27
28const QEvent::Type Event_Grab_Completed = static_cast<QEvent::Type>(QEvent::User + 1);
29
31{
32public:
34 : cacheEntry(nullptr)
35 , qmlEngine(nullptr)
36 , texture(nullptr)
37 , devicePixelRatio(1.0)
38 {
39 }
40
42 {
43 // Remove the unmanaged heap size we've added when creating the image in
44 // QQuickItemGrabResult::render(). If render() was never called the image is empty and
45 // its size is 0.
46 if (hasCallback())
47 qmlEngine->handle()->memoryManager->changeUnmanagedHeapSizeUsage(-image.sizeInBytes());
48 delete cacheEntry;
49 }
50
51 // Should be called under a locked mutex!
52 bool hasCallback() const { return qmlEngine && callback.isCallable(); }
53
54 // Should be called under a locked mutex!
55 void ensureImageInCache() const {
56 if (url.isEmpty() && !image.isNull()) {
57 url.setScheme(QQuickPixmap::itemGrabberScheme);
58 url.setPath(QVariant::fromValue(item.data()).toString());
59 static uint counter = 0;
60 url.setFragment(QString::number(++counter));
61 cacheEntry = new QQuickPixmap(url, image);
62 }
63 }
64
65 static QQuickItemGrabResult *create(QQuickItem *item, const QSize &size);
66
68
70
71 mutable QUrl url;
73
76
83
86};
87
88/*!
89 * \qmlproperty url QtQuick::ItemGrabResult::url
90 *
91 * This property holds a URL which can be used in conjunction with
92 * URL based image consumers, such as the QtQuick::Image type.
93 *
94 * The URL is valid while there is a reference in QML or JavaScript
95 * to the ItemGrabResult or while the image the URL references is
96 * actively used.
97 *
98 * The URL does not represent a valid file or location to read it from, it
99 * is primarily a key to access images through Qt Quick's image-based types.
100 */
101
102/*!
103 * \property QQuickItemGrabResult::url
104 *
105 * This property holds a URL which can be used in conjunction with
106 * URL based image consumers, such as the QtQuick::Image type.
107 *
108 * The URL is valid until the QQuickItemGrabResult object is deleted.
109 *
110 * The URL does not represent a valid file or location to read it from, it
111 * is primarily a key to access images through Qt Quick's image-based types.
112 */
113
114/*!
115 * \qmlproperty variant QtQuick::ItemGrabResult::image
116 *
117 * This property holds the pixel results from a grab in the
118 * form of a QImage.
119 */
120
121/*!
122 * \property QQuickItemGrabResult::image
123 *
124 * This property holds the pixel results from a grab.
125 *
126 * If the grab is not yet complete or if it failed,
127 * a null image is returned (\c {image.isNull()} will return \c true).
128 */
129
130/*!
131 \class QQuickItemGrabResult
132 \inmodule QtQuick
133 \brief The QQuickItemGrabResult contains the result from QQuickItem::grabToImage().
134
135 \sa QQuickItem::grabToImage()
136 */
137
138/*!
139 * \fn void QQuickItemGrabResult::ready()
140 *
141 * This signal is emitted when the grab has completed.
142 */
143
144/*!
145 * \qmltype ItemGrabResult
146 * \nativetype QQuickItemGrabResult
147 * \inherits QtObject
148 * \inqmlmodule QtQuick
149 * \ingroup qtquick-visual
150 * \brief Contains the results from a call to Item::grabToImage().
151 *
152 * The ItemGrabResult is a small container used to encapsulate
153 * the results from Item::grabToImage().
154 *
155 * \sa Item::grabToImage()
156 */
157
158QQuickItemGrabResult::QQuickItemGrabResult(QObject *parent)
159 : QObject(*new QQuickItemGrabResultPrivate, parent)
160{
161}
162
163/*!
164 * \qmlmethod bool QtQuick::ItemGrabResult::saveToFile(fileName)
165 *
166 * Saves the grab result as an image to \a fileName. Returns \c true
167 * if successful; otherwise returns \c false.
168 */
169
170// ### Qt 7: remove and keep only QUrl overload
171/*!
172 * Saves the grab result as an image to \a fileName. Returns \c true
173 * if successful; otherwise returns \c false.
174 *
175 * \note In Qt versions prior to 5.9, this function is marked as non-\c{const}.
176 */
177bool QQuickItemGrabResult::saveToFile(const QString &fileName) const
178{
179 Q_D(const QQuickItemGrabResult);
180 if (fileName.startsWith(QLatin1String("file:/")))
181 return saveToFile(QUrl(fileName));
182 QMutexLocker locker(&d->mutex);
183 return d->image.save(fileName);
184}
185
186/*!
187 * \since 6.2
188 * Saves the grab result as an image to \a filePath, which must refer to a
189 * \l{QUrl::isLocalFile}{local file name} with a
190 * \l{QImageWriter::supportedImageFormats()}{supported image format} extension.
191 * Returns \c true if successful; otherwise returns \c false.
192 */
193bool QQuickItemGrabResult::saveToFile(const QUrl &filePath) const
194{
195 Q_D(const QQuickItemGrabResult);
196 if (!filePath.isLocalFile()) {
197 qWarning() << "saveToFile can only save to a file on the local filesystem";
198 return false;
199 }
200 QMutexLocker locker(&d->mutex);
201 return d->image.save(filePath.toLocalFile());
202}
203
204#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
205#if QT_DEPRECATED_SINCE(5, 15)
206/*!
207 * \overload
208 * \internal
209 */
210bool QQuickItemGrabResult::saveToFile(const QString &fileName)
211{
212 return std::as_const(*this).saveToFile(fileName);
213}
214#endif
215#endif // < Qt 6
216
217QUrl QQuickItemGrabResult::url() const
218{
219 Q_D(const QQuickItemGrabResult);
220 QMutexLocker locker(&d->mutex);
221 d->ensureImageInCache();
222 return d->url;
223}
224
225QImage QQuickItemGrabResult::image() const
226{
227 Q_D(const QQuickItemGrabResult);
228 QMutexLocker locker(&d->mutex);
229 return d->image;
230}
231
232/*!
233 * \internal
234 */
235bool QQuickItemGrabResult::event(QEvent *e)
236{
237 Q_D(QQuickItemGrabResult);
238 if (e->type() == Event_Grab_Completed) {
239 // We cannot hold the mutex while emitting the signal or calling the
240 // callback, because they can immediately call various getters.
241 QJSValue callback;
242 QQmlEngine *engine = nullptr;
243 if (QMutexLocker locker(&d->mutex); d->hasCallback()) {
244 callback = d->callback;
245 engine = d->qmlEngine;
246 }
247 if (engine) {
248 // We have a JS callback. Transfer ownership to JavaScript to follow the documentation.
249 // The user should be able to store the object in QML, but we also want to GC it
250 // eventually.
251 QQmlEngine::setObjectOwnership(this, QQmlEngine::JavaScriptOwnership);
252 setParent(nullptr);
253 callback.call(QJSValueList() << engine->newQObject(this));
254 } else {
255 Q_EMIT ready();
256 }
257 return true;
258 }
259 return QObject::event(e);
260}
261
262/*!
263 \internal
264 This method is called from QSGRenderThread
265*/
266void QQuickItemGrabResult::setup()
267{
268 Q_D(QQuickItemGrabResult);
269 QMutexLocker locker(&d->mutex);
270 if (!d->item) {
271 disconnect(d->setupConnection);
272 disconnect(d->renderConnection);
273 QCoreApplication::postEvent(this, new QEvent(Event_Grab_Completed));
274 return;
275 }
276
277 QSGRenderContext *rc = QQuickWindowPrivate::get(d->window.data())->context;
278 d->devicePixelRatio = d->window->effectiveDevicePixelRatio();
279 d->texture = rc->sceneGraphContext()->createLayer(rc);
280 d->texture->setDevicePixelRatio(d->devicePixelRatio);
281 d->texture->setItem(QQuickItemPrivate::get(d->item)->itemNode());
282 d->itemSize = QSizeF(d->item->width(), d->item->height());
283}
284
285/*!
286 \internal
287 This method is called from QSGRenderThread
288*/
289void QQuickItemGrabResult::render()
290{
291 Q_D(QQuickItemGrabResult);
292 QMutexLocker locker(&d->mutex);
293 if (!d->texture)
294 return;
295
296 d->texture->setRect(QRectF(0, d->itemSize.height(), d->itemSize.width(), -d->itemSize.height()));
297 const QSize minSize = QQuickWindowPrivate::get(d->window.data())->context->sceneGraphContext()->minimumFBOSize();
298 const QSize effectiveTextureSize = d->textureSize * d->devicePixelRatio;
299 d->texture->setSize(QSize(qMax(minSize.width(), effectiveTextureSize.width()),
300 qMax(minSize.height(), effectiveTextureSize.height())));
301 d->texture->scheduleUpdate();
302 d->texture->updateTexture();
303
304 const qsizetype oldImageSize = d->image.sizeInBytes();
305 d->image = d->texture->toImage();
306 d->image.setDevicePixelRatio(d->devicePixelRatio);
307 const qsizetype newImageSize = d->image.sizeInBytes();
308 if (d->hasCallback()) {
309 // If we have a callback, we assume that it's going to be called, eventually. If you post
310 // an event that never arrives, you have worse problems. Based on that assumption, we track
311 // the (potentially large) image as unmanaged heap size already now. This is the only place
312 // where we can determine the size. In the dtor we need to "untrack" the image if it was
313 // tracked. So either we need to store an extra bit that tells us whether the callback was
314 // called or we need to live with stale unmanaged heap size in the unlikely case that it's
315 // never called. We choose the latter.
316 d->qmlEngine->handle()->memoryManager->changeUnmanagedHeapSizeUsage(
317 newImageSize - oldImageSize);
318 }
319
320 delete d->texture;
321 d->texture = nullptr;
322
323 disconnect(d->setupConnection);
324 disconnect(d->renderConnection);
325 QCoreApplication::postEvent(this, new QEvent(Event_Grab_Completed));
326}
327
328QQuickItemGrabResult *QQuickItemGrabResultPrivate::create(QQuickItem *item, const QSize &targetSize)
329{
330 QSize size = targetSize;
331 if (size.isEmpty())
332 size = QSize(item->width(), item->height());
333
334 if (size.width() < 1 || size.height() < 1) {
335 qmlWarning(item) << "grabToImage: item has invalid dimensions";
336 return nullptr;
337 }
338
339 if (!item->window()) {
340 qmlWarning(item) << "grabToImage: item is not attached to a window";
341 return nullptr;
342 }
343
344 QWindow *effectiveWindow = item->window();
345 if (QWindow *renderWindow = QQuickRenderControl::renderWindowFor(item->window()))
346 effectiveWindow = renderWindow;
347
348 if (!effectiveWindow->isVisible()) {
349 qmlWarning(item) << "grabToImage: item's window is not visible";
350 return nullptr;
351 }
352
353 // Initially parent to item so that we don't leak it if Event_Grab_Completed never arrives or
354 // the callback can't be called.
355 QQuickItemGrabResult *result = new QQuickItemGrabResult(item);
356 QQuickItemGrabResultPrivate *d = result->d_func();
357 // no need to lock here, since we didn't do the connection yet, so it cannot be
358 // accessed from multiple threads
359 d->item = item;
360 d->window = item->window();
361 d->textureSize = size;
362
363 QQuickItemPrivate::get(item)->refFromEffectItem(false);
364
365 // trigger sync & render
366 item->window()->update();
367
368 return result;
369}
370
371/*!
372 * Grabs the item into an in-memory image.
373 *
374 * The grab happens asynchronously and the signal QQuickItemGrabResult::ready()
375 * is emitted when the grab has been completed.
376 *
377 * Use \a targetSize to specify the size of the target image. By default, the
378 * result will have the same size as item.
379 *
380 * If the grab could not be initiated, the function returns \c null.
381 *
382 * \note This function will render the item to an offscreen surface and
383 * copy that surface from the GPU's memory into the CPU's memory, which can
384 * be quite costly. For "live" preview, use \l {QtQuick::Item::layer.enabled} {layers}
385 * or ShaderEffectSource.
386 *
387 * \sa QQuickWindow::grabWindow()
388 */
389QSharedPointer<QQuickItemGrabResult> QQuickItem::grabToImage(const QSize &targetSize)
390{
391 QQuickItemGrabResult *result = QQuickItemGrabResultPrivate::create(this, targetSize);
392 if (!result)
393 return QSharedPointer<QQuickItemGrabResult>();
394
395 QQuickItemGrabResultPrivate *d = result->d_func();
396 auto sharedResult = QSharedPointer<QQuickItemGrabResult>(result);
397 result->setParent(nullptr); // the smart pointer now manages the lifetime
398
399 auto weakResult = QWeakPointer(sharedResult);
400 QMutexLocker locker(&d->mutex);
401 d->setupConnection = connect(window(), &QQuickWindow::beforeSynchronizing,
402 result, [weakResult] {
403 if (auto strong = weakResult.toStrongRef(); strong)
404 strong->setup();
405 }, Qt::DirectConnection);
406 d->renderConnection = connect(window(), &QQuickWindow::afterRendering,
407 result, [weakResult] {
408 if (auto strong = weakResult.toStrongRef(); strong)
409 strong->render();
410 }, Qt::DirectConnection);
411
412 return sharedResult;
413}
414
415/*!
416 * \qmlmethod bool QtQuick::Item::grabToImage(callback, targetSize)
417 *
418 * Grabs the item into an in-memory image.
419 *
420 * The grab happens asynchronously and the JavaScript function \a callback is
421 * invoked when the grab is completed. The callback takes one argument, which
422 * is the result of the grab operation; an \l ItemGrabResult object.
423 *
424 * Use \a targetSize to specify the size of the target image. By default, the result
425 * will have the same size as the item.
426 *
427 * If the grab could not be initiated, the function returns \c false.
428 *
429 * The following snippet shows how to grab an item and store the results in
430 * a file:
431 *
432 * \snippet qml/item/itemGrab.qml grab-to-file
433 *
434 * The following snippet shows how to grab an item and use the results in
435 * another image element:
436 *
437 * \snippet qml/item/itemGrab.qml grab-to-image
438 *
439 * \note This function will render the item to an offscreen surface and
440 * copy that surface from the GPU's memory into the CPU's memory, which can
441 * be quite costly. For "live" preview, use \l {QtQuick::Item::layer.enabled} {layers}
442 * or ShaderEffectSource.
443 */
444
445/*!
446 * \internal
447 * Only visible from QML.
448 */
449bool QQuickItem::grabToImage(const QJSValue &callback, const QSize &targetSize)
450{
451 QQmlEngine *engine = qmlEngine(this);
452 if (!engine) {
453 qmlWarning(this) << "grabToImage: item has no QML engine";
454 return false;
455 }
456
457 if (!callback.isCallable()) {
458 qmlWarning(this) << "grabToImage: 'callback' is not a function";
459 return false;
460 }
461
462 QSize size = targetSize;
463 if (size.isEmpty())
464 size = QSize(width(), height());
465
466 if (size.width() < 1 || size.height() < 1) {
467 qmlWarning(this) << "grabToImage: item has invalid dimensions";
468 return false;
469 }
470
471 if (!window()) {
472 qmlWarning(this) << "grabToImage: item is not attached to a window";
473 return false;
474 }
475
476 QQuickItemGrabResult *result = QQuickItemGrabResultPrivate::create(this, size);
477 if (!result)
478 return false;
479
480 // Do not transfer ownership to JavaScript here, yet. There is no reference to the object
481 // We don't want the GC to collect it while the grab is still in flight.
482 QQuickItemGrabResultPrivate *d = result->d_func();
483
484 QMutexLocker locker(&d->mutex);
485 d->qmlEngine = engine;
486 d->callback = callback;
487
488 d->setupConnection = connect(window(), &QQuickWindow::beforeSynchronizing,
489 result, &QQuickItemGrabResult::setup, Qt::DirectConnection);
490 d->renderConnection = connect(window(), &QQuickWindow::afterRendering,
491 result, &QQuickItemGrabResult::render, Qt::DirectConnection);
492
493 return true;
494}
495
496QT_END_NAMESPACE
497
498#include "moc_qquickitemgrabresult.cpp"
QMetaObject::Connection setupConnection
QPointer< QQuickWindow > window
static QQuickItemGrabResult * create(QQuickItem *item, const QSize &size)
QMetaObject::Connection renderConnection
Combined button and popup list for selecting options.
QT_BEGIN_NAMESPACE const QEvent::Type Event_Grab_Completed