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

source code of qtdeclarative/src/quick/items/qquickitemgrabresult.cpp