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/qpointer.h>
24
26
27const QEvent::Type Event_Grab_Completed = static_cast<QEvent::Type>(QEvent::User + 1);
28
30{
31public:
33 : cacheEntry(nullptr)
34 , qmlEngine(nullptr)
35 , texture(nullptr)
36 , devicePixelRatio(1.0)
37 {
38 }
39
41 {
42 // Remove the unmanaged heap size we've added when creating the image in
43 // QQuickItemGrabResult::render(). If render() was never called the image is empty and
44 // its size is 0.
45 if (hasCallback())
46 qmlEngine->handle()->memoryManager->changeUnmanagedHeapSizeUsage(-image.sizeInBytes());
47 delete cacheEntry;
48 }
49
50 bool hasCallback() const { return qmlEngine && callback.isCallable(); }
51
52 void ensureImageInCache() const {
53 if (url.isEmpty() && !image.isNull()) {
54 url.setScheme(QQuickPixmap::itemGrabberScheme);
55 url.setPath(QVariant::fromValue(item.data()).toString());
56 static uint counter = 0;
57 url.setFragment(QString::number(++counter));
58 cacheEntry = new QQuickPixmap(url, image);
59 }
60 }
61
62 static QQuickItemGrabResult *create(QQuickItem *item, const QSize &size);
63
65
66 mutable QUrl url;
68
71
78};
79
80/*!
81 * \qmlproperty url QtQuick::ItemGrabResult::url
82 *
83 * This property holds a URL which can be used in conjunction with
84 * URL based image consumers, such as the QtQuick::Image type.
85 *
86 * The URL is valid while there is a reference in QML or JavaScript
87 * to the ItemGrabResult or while the image the URL references is
88 * actively used.
89 *
90 * The URL does not represent a valid file or location to read it from, it
91 * is primarily a key to access images through Qt Quick's image-based types.
92 */
93
94/*!
95 * \property QQuickItemGrabResult::url
96 *
97 * This property holds a URL which can be used in conjunction with
98 * URL based image consumers, such as the QtQuick::Image type.
99 *
100 * The URL is valid until the QQuickItemGrabResult object is deleted.
101 *
102 * The URL does not represent a valid file or location to read it from, it
103 * is primarily a key to access images through Qt Quick's image-based types.
104 */
105
106/*!
107 * \qmlproperty variant QtQuick::ItemGrabResult::image
108 *
109 * This property holds the pixel results from a grab in the
110 * form of a QImage.
111 */
112
113/*!
114 * \property QQuickItemGrabResult::image
115 *
116 * This property holds the pixel results from a grab.
117 *
118 * If the grab is not yet complete or if it failed,
119 * a null image is returned (\c {image.isNull()} will return \c true).
120 */
121
122/*!
123 \class QQuickItemGrabResult
124 \inmodule QtQuick
125 \brief The QQuickItemGrabResult contains the result from QQuickItem::grabToImage().
126
127 \sa QQuickItem::grabToImage()
128 */
129
130/*!
131 * \fn void QQuickItemGrabResult::ready()
132 *
133 * This signal is emitted when the grab has completed.
134 */
135
136/*!
137 * \qmltype ItemGrabResult
138 * \nativetype QQuickItemGrabResult
139 * \inherits QtObject
140 * \inqmlmodule QtQuick
141 * \ingroup qtquick-visual
142 * \brief Contains the results from a call to Item::grabToImage().
143 *
144 * The ItemGrabResult is a small container used to encapsulate
145 * the results from Item::grabToImage().
146 *
147 * \sa Item::grabToImage()
148 */
149
150QQuickItemGrabResult::QQuickItemGrabResult(QObject *parent)
151 : QObject(*new QQuickItemGrabResultPrivate, parent)
152{
153}
154
155/*!
156 * \qmlmethod bool QtQuick::ItemGrabResult::saveToFile(fileName)
157 *
158 * Saves the grab result as an image to \a fileName. Returns \c true
159 * if successful; otherwise returns \c false.
160 */
161
162// ### Qt 7: remove and keep only QUrl overload
163/*!
164 * Saves the grab result as an image to \a fileName. Returns \c true
165 * if successful; otherwise returns \c false.
166 *
167 * \note In Qt versions prior to 5.9, this function is marked as non-\c{const}.
168 */
169bool QQuickItemGrabResult::saveToFile(const QString &fileName) const
170{
171 Q_D(const QQuickItemGrabResult);
172 if (fileName.startsWith(QLatin1String("file:/")))
173 return saveToFile(QUrl(fileName));
174 return d->image.save(fileName);
175}
176
177/*!
178 * \since 6.2
179 * Saves the grab result as an image to \a filePath, which must refer to a
180 * \l{QUrl::isLocalFile}{local file name} with a
181 * \l{QImageWriter::supportedImageFormats()}{supported image format} extension.
182 * Returns \c true if successful; otherwise returns \c false.
183 */
184bool QQuickItemGrabResult::saveToFile(const QUrl &filePath) const
185{
186 Q_D(const QQuickItemGrabResult);
187 if (!filePath.isLocalFile()) {
188 qWarning() << "saveToFile can only save to a file on the local filesystem";
189 return false;
190 }
191 return d->image.save(filePath.toLocalFile());
192}
193
194#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
195#if QT_DEPRECATED_SINCE(5, 15)
196/*!
197 * \overload
198 * \internal
199 */
200bool QQuickItemGrabResult::saveToFile(const QString &fileName)
201{
202 return std::as_const(*this).saveToFile(fileName);
203}
204#endif
205#endif // < Qt 6
206
207QUrl QQuickItemGrabResult::url() const
208{
209 Q_D(const QQuickItemGrabResult);
210 d->ensureImageInCache();
211 return d->url;
212}
213
214QImage QQuickItemGrabResult::image() const
215{
216 Q_D(const QQuickItemGrabResult);
217 return d->image;
218}
219
220/*!
221 * \internal
222 */
223bool QQuickItemGrabResult::event(QEvent *e)
224{
225 Q_D(QQuickItemGrabResult);
226 if (e->type() == Event_Grab_Completed) {
227 if (d->hasCallback()) {
228 // JS callback
229 d->callback.call(QJSValueList() << d->qmlEngine->newQObject(this));
230 QQmlEngine::setObjectOwnership(this, QQmlEngine::JavaScriptOwnership);
231 } else {
232 Q_EMIT ready();
233 }
234 return true;
235 }
236 return QObject::event(e);
237}
238
239void QQuickItemGrabResult::setup()
240{
241 Q_D(QQuickItemGrabResult);
242 if (!d->item) {
243 disconnect(d->window.data(), &QQuickWindow::beforeSynchronizing, this, &QQuickItemGrabResult::setup);
244 disconnect(d->window.data(), &QQuickWindow::afterRendering, this, &QQuickItemGrabResult::render);
245 QCoreApplication::postEvent(this, new QEvent(Event_Grab_Completed));
246 return;
247 }
248
249 QSGRenderContext *rc = QQuickWindowPrivate::get(d->window.data())->context;
250 d->devicePixelRatio = d->window->effectiveDevicePixelRatio();
251 d->texture = rc->sceneGraphContext()->createLayer(rc);
252 d->texture->setDevicePixelRatio(d->devicePixelRatio);
253 d->texture->setItem(QQuickItemPrivate::get(d->item)->itemNode());
254 d->itemSize = QSizeF(d->item->width(), d->item->height());
255}
256
257void QQuickItemGrabResult::render()
258{
259 Q_D(QQuickItemGrabResult);
260 if (!d->texture)
261 return;
262
263 d->texture->setRect(QRectF(0, d->itemSize.height(), d->itemSize.width(), -d->itemSize.height()));
264 const QSize minSize = QQuickWindowPrivate::get(d->window.data())->context->sceneGraphContext()->minimumFBOSize();
265 const QSize effectiveTextureSize = d->textureSize * d->devicePixelRatio;
266 d->texture->setSize(QSize(qMax(minSize.width(), effectiveTextureSize.width()),
267 qMax(minSize.height(), effectiveTextureSize.height())));
268 d->texture->scheduleUpdate();
269 d->texture->updateTexture();
270
271 const qsizetype oldImageSize = d->image.sizeInBytes();
272 d->image = d->texture->toImage();
273 d->image.setDevicePixelRatio(d->devicePixelRatio);
274 const qsizetype newImageSize = d->image.sizeInBytes();
275 if (d->hasCallback()) {
276 // If we have a callback, we assume that it's going to be called, eventually. If you post
277 // an event that never arrives, you have worse problems. Based on that assumption, we track
278 // the (potentially large) image as unmanaged heap size already now. This is the only place
279 // where we can determine the size. In the dtor we need to "untrack" the image if it was
280 // tracked. So either we need to store an extra bit that tells us whether the callback was
281 // called or we need to live with stale unmanaged heap size in the unlikely case that it's
282 // never called. We choose the latter.
283 d->qmlEngine->handle()->memoryManager->changeUnmanagedHeapSizeUsage(
284 newImageSize - oldImageSize);
285 }
286
287 delete d->texture;
288 d->texture = nullptr;
289
290 disconnect(d->window.data(), &QQuickWindow::beforeSynchronizing, this, &QQuickItemGrabResult::setup);
291 disconnect(d->window.data(), &QQuickWindow::afterRendering, this, &QQuickItemGrabResult::render);
292 QCoreApplication::postEvent(this, new QEvent(Event_Grab_Completed));
293}
294
295QQuickItemGrabResult *QQuickItemGrabResultPrivate::create(QQuickItem *item, const QSize &targetSize)
296{
297 QSize size = targetSize;
298 if (size.isEmpty())
299 size = QSize(item->width(), item->height());
300
301 if (size.width() < 1 || size.height() < 1) {
302 qmlWarning(item) << "grabToImage: item has invalid dimensions";
303 return nullptr;
304 }
305
306 if (!item->window()) {
307 qmlWarning(item) << "grabToImage: item is not attached to a window";
308 return nullptr;
309 }
310
311 QWindow *effectiveWindow = item->window();
312 if (QWindow *renderWindow = QQuickRenderControl::renderWindowFor(item->window()))
313 effectiveWindow = renderWindow;
314
315 if (!effectiveWindow->isVisible()) {
316 qmlWarning(item) << "grabToImage: item's window is not visible";
317 return nullptr;
318 }
319
320 QQuickItemGrabResult *result = new QQuickItemGrabResult();
321 QQuickItemGrabResultPrivate *d = result->d_func();
322 d->item = item;
323 d->window = item->window();
324 d->textureSize = size;
325
326 QQuickItemPrivate::get(item)->refFromEffectItem(false);
327
328 // trigger sync & render
329 item->window()->update();
330
331 return result;
332}
333
334/*!
335 * Grabs the item into an in-memory image.
336 *
337 * The grab happens asynchronously and the signal QQuickItemGrabResult::ready()
338 * is emitted when the grab has been completed.
339 *
340 * Use \a targetSize to specify the size of the target image. By default, the
341 * result will have the same size as item.
342 *
343 * If the grab could not be initiated, the function returns \c null.
344 *
345 * \note This function will render the item to an offscreen surface and
346 * copy that surface from the GPU's memory into the CPU's memory, which can
347 * be quite costly. For "live" preview, use \l {QtQuick::Item::layer.enabled} {layers}
348 * or ShaderEffectSource.
349 *
350 * \sa QQuickWindow::grabWindow()
351 */
352QSharedPointer<QQuickItemGrabResult> QQuickItem::grabToImage(const QSize &targetSize)
353{
354 QQuickItemGrabResult *result = QQuickItemGrabResultPrivate::create(this, targetSize);
355 if (!result)
356 return QSharedPointer<QQuickItemGrabResult>();
357
358 connect(window(), &QQuickWindow::beforeSynchronizing, result, &QQuickItemGrabResult::setup, Qt::DirectConnection);
359 connect(window(), &QQuickWindow::afterRendering, result, &QQuickItemGrabResult::render, Qt::DirectConnection);
360
361 return QSharedPointer<QQuickItemGrabResult>(result);
362}
363
364/*!
365 * \qmlmethod bool QtQuick::Item::grabToImage(callback, targetSize)
366 *
367 * Grabs the item into an in-memory image.
368 *
369 * The grab happens asynchronously and the JavaScript function \a callback is
370 * invoked when the grab is completed. The callback takes one argument, which
371 * is the result of the grab operation; an \l ItemGrabResult object.
372 *
373 * Use \a targetSize to specify the size of the target image. By default, the result
374 * will have the same size as the item.
375 *
376 * If the grab could not be initiated, the function returns \c false.
377 *
378 * The following snippet shows how to grab an item and store the results in
379 * a file:
380 *
381 * \snippet qml/item/itemGrab.qml grab-to-file
382 *
383 * The following snippet shows how to grab an item and use the results in
384 * another image element:
385 *
386 * \snippet qml/item/itemGrab.qml grab-to-image
387 *
388 * \note This function will render the item to an offscreen surface and
389 * copy that surface from the GPU's memory into the CPU's memory, which can
390 * be quite costly. For "live" preview, use \l {QtQuick::Item::layer.enabled} {layers}
391 * or ShaderEffectSource.
392 */
393
394/*!
395 * \internal
396 * Only visible from QML.
397 */
398bool QQuickItem::grabToImage(const QJSValue &callback, const QSize &targetSize)
399{
400 QQmlEngine *engine = qmlEngine(this);
401 if (!engine) {
402 qmlWarning(this) << "grabToImage: item has no QML engine";
403 return false;
404 }
405
406 if (!callback.isCallable()) {
407 qmlWarning(this) << "grabToImage: 'callback' is not a function";
408 return false;
409 }
410
411 QSize size = targetSize;
412 if (size.isEmpty())
413 size = QSize(width(), height());
414
415 if (size.width() < 1 || size.height() < 1) {
416 qmlWarning(this) << "grabToImage: item has invalid dimensions";
417 return false;
418 }
419
420 if (!window()) {
421 qmlWarning(this) << "grabToImage: item is not attached to a window";
422 return false;
423 }
424
425 QQuickItemGrabResult *result = QQuickItemGrabResultPrivate::create(this, size);
426 if (!result)
427 return false;
428
429 connect(window(), &QQuickWindow::beforeSynchronizing, result, &QQuickItemGrabResult::setup, Qt::DirectConnection);
430 connect(window(), &QQuickWindow::afterRendering, result, &QQuickItemGrabResult::render, Qt::DirectConnection);
431
432 QQuickItemGrabResultPrivate *d = result->d_func();
433 d->qmlEngine = engine;
434 d->callback = callback;
435 return true;
436}
437
438QT_END_NAMESPACE
439
440#include "moc_qquickitemgrabresult.cpp"
QPointer< QQuickWindow > window
static QQuickItemGrabResult * create(QQuickItem *item, const QSize &size)
Combined button and popup list for selecting options.
QT_BEGIN_NAMESPACE const QEvent::Type Event_Grab_Completed