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
qquickborderimage.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
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
7
8#include <QtQml/qqmlinfo.h>
9#include <QtQml/qqmlfile.h>
10#include <QtQml/qqmlengine.h>
11#if QT_CONFIG(qml_network)
12#include <QtNetwork/qnetworkreply.h>
13#endif
14#include <QtCore/qfile.h>
15#include <QtCore/qmath.h>
16#include <QtGui/qguiapplication.h>
17
18#include <private/qqmlglobal_p.h>
19#include <private/qsgadaptationlayer_p.h>
20
22
23
24/*!
25 \qmltype BorderImage
26 \nativetype QQuickBorderImage
27 \inqmlmodule QtQuick
28 \brief Paints a border based on an image.
29 \inherits Item
30 \ingroup qtquick-visual
31
32 The BorderImage type is used to create borders out of images by scaling or tiling
33 parts of each image.
34
35 A BorderImage breaks a source image, specified using the \l source property,
36 into 9 regions, as shown below:
37
38 \image declarative-scalegrid.png
39
40 When the image is scaled, regions of the source image are scaled or tiled to
41 create the displayed border image in the following way:
42
43 \list
44 \li The corners (regions 1, 3, 7, and 9) are not scaled at all.
45 \li Regions 2 and 8 are scaled according to
46 \l{BorderImage::horizontalTileMode}{horizontalTileMode}.
47 \li Regions 4 and 6 are scaled according to
48 \l{BorderImage::verticalTileMode}{verticalTileMode}.
49 \li The middle (region 5) is scaled according to both
50 \l{BorderImage::horizontalTileMode}{horizontalTileMode} and
51 \l{BorderImage::verticalTileMode}{verticalTileMode}.
52 \endlist
53
54 The regions of the image are defined using the \l border property group, which
55 describes the distance from each edge of the source image to use as a border.
56
57 \section1 Example Usage
58
59 The following examples show the effects of the different modes on an image.
60 Guide lines are overlaid onto the image to show the different regions of the
61 image as described above.
62
63 \beginfloatleft
64 \image qml-borderimage-normal-image.png
65 \endfloat
66
67 For comparison, an unscaled image is displayed using a simple Image item.
68 Here we have overlaid lines to show how we'd like to break it up with BorderImage:
69
70 \snippet qml/borderimage/normal-image.qml normal image
71
72 \clearfloat
73 \beginfloatleft
74 \image qml-borderimage-scaled.png
75 \endfloat
76
77 But when a BorderImage is used to display the image, the \l border property is
78 used to determine the parts of the image that will lie inside the unscaled corner
79 areas, and the parts that will be stretched horizontally and vertically.
80 Then, you can give it a size that is
81 larger than the original image. Since the \l horizontalTileMode property is set to
82 \l{BorderImage::horizontalTileMode}{BorderImage.Stretch}, the parts of image in
83 regions 2 and 8 are stretched horizontally. Since the \l verticalTileMode property
84 is set to \l{BorderImage::verticalTileMode}{BorderImage.Stretch}, the parts of image
85 in regions 4 and 6 are stretched vertically:
86
87 \snippet qml/borderimage/borderimage-scaled.qml scaled border image
88
89 \clearfloat
90 \beginfloatleft
91 \image qml-borderimage-tiled.png
92 \endfloat
93
94 Again, a large BorderImage is used to display the image. With the
95 \l horizontalTileMode property set to \l{BorderImage::horizontalTileMode}{BorderImage.Repeat},
96 the parts of image in regions 2 and 8 are tiled so that they fill the space at the
97 top and bottom of the item. Similarly, the \l verticalTileMode property is set to
98 \l{BorderImage::verticalTileMode}{BorderImage.Repeat}, so the parts of image in regions
99 4 and 6 are tiled to fill the space at the left and right of the item:
100
101 \snippet qml/borderimage/borderimage-tiled.qml tiled border image
102
103 \clearfloat
104 \beginfloatleft
105 \image qml-borderimage-rounded.png
106 \endfloat
107
108 In some situations, the width of regions 2 and 8 may not be an exact multiple of the width
109 of the corresponding regions in the source image. Similarly, the height of regions 4 and 6
110 may not be an exact multiple of the height of the corresponding regions. If you use
111 \l{BorderImage::horizontalTileMode}{BorderImage.Round} mode, it will choose an integer
112 number of tiles and shrink them to fit:
113
114 \snippet qml/borderimage/borderimage-rounded.qml tiled border image
115
116 \clearfloat
117
118 The Border Image example in \l{Qt Quick Examples - Image Elements} shows how a BorderImage
119 can be used to simulate a shadow effect on a rectangular item.
120
121 \section1 Image Loading
122
123 The source image may not be loaded instantaneously, depending on its original location.
124 Loading progress can be monitored with the \l progress property.
125
126 \sa Image, AnimatedImage
127 */
128
129/*!
130 \qmlproperty bool QtQuick::BorderImage::asynchronous
131
132 Specifies that images on the local filesystem should be loaded
133 asynchronously in a separate thread. The default value is
134 false, causing the user interface thread to block while the
135 image is loaded. Setting \a asynchronous to true is useful where
136 maintaining a responsive user interface is more desirable
137 than having images immediately visible.
138
139 Note that this property is only valid for images read from the
140 local filesystem. Images loaded via a network resource (e.g. HTTP)
141 are always loaded asynchronously.
142*/
143QQuickBorderImage::QQuickBorderImage(QQuickItem *parent)
144: QQuickImageBase(*(new QQuickBorderImagePrivate), parent)
145{
146 connect(this, &QQuickImageBase::sourceSizeChanged, this, &QQuickBorderImage::sourceSizeChanged);
147}
148
149QQuickBorderImage::~QQuickBorderImage()
150{
151#if QT_CONFIG(qml_network)
152 Q_D(QQuickBorderImage);
153 if (d->sciReply)
154 d->sciReply->deleteLater();
155#endif
156}
157
158/*!
159 \qmlproperty enumeration QtQuick::BorderImage::status
160
161 This property describes the status of image loading. It can be one of:
162
163 \value BorderImage.Null No image has been set
164 \value BorderImage.Ready The image has been loaded
165 \value BorderImage.Loading The image is currently being loaded
166 \value BorderImage.Error An error occurred while loading the image
167
168 \sa progress
169*/
170
171/*!
172 \qmlproperty real QtQuick::BorderImage::progress
173
174 This property holds the progress of image loading, from 0.0 (nothing loaded)
175 to 1.0 (finished).
176
177 \sa status
178*/
179
180/*!
181 \qmlproperty bool QtQuick::BorderImage::smooth
182
183 This property holds whether the image is smoothly filtered when scaled or
184 transformed. Smooth filtering gives better visual quality, but it may be slower
185 on some hardware. If the image is displayed at its natural size, this property
186 has no visual or performance effect.
187
188 By default, this property is set to true.
189*/
190
191/*!
192 \qmlproperty bool QtQuick::BorderImage::cache
193
194 Specifies whether the image should be cached. The default value is
195 true. Setting \a cache to false is useful when dealing with large images,
196 to make sure that they aren't cached at the expense of small 'ui element' images.
197*/
198
199/*!
200 \qmlproperty bool QtQuick::BorderImage::mirror
201
202 This property holds whether the image should be horizontally inverted
203 (effectively displaying a mirrored image).
204
205 The default value is false.
206*/
207
208/*!
209 \qmlproperty url QtQuick::BorderImage::source
210
211 This property holds the URL that refers to the source image.
212
213 BorderImage can handle any image format supported by Qt, loaded from any
214 URL scheme supported by Qt.
215
216 This property can also refer to a \c .sci file — a QML-specific,
217 text-based format that embeds the border values, the source image,
218 and tile rules directly within the file. When using a \c .sci file,
219 the BorderImage reads the border information from the file itself,
220 so specifying border properties in QML is unnecessary.
221
222
223 The following .sci file sets the borders to 10 on each side for the
224 image \c picture.png:
225
226 \code
227 border.left: 10
228 border.top: 10
229 border.bottom: 10
230 border.right: 10
231 source: "picture.png"
232 \endcode
233
234 The URL may be absolute, or relative to the URL of the component.
235
236 \sa QQuickImageProvider
237*/
238
239/*!
240 \qmlproperty size QtQuick::BorderImage::sourceSize
241
242 This property holds the actual width and height of the loaded image.
243
244 In BorderImage, this property is read-only.
245
246 \sa Image::sourceSize
247*/
248void QQuickBorderImage::setSource(const QUrl &url)
249{
250 Q_D(QQuickBorderImage);
251
252 if (url == d->url)
253 return;
254
255#if QT_CONFIG(qml_network)
256 if (d->sciReply) {
257 d->sciReply->deleteLater();
258 d->sciReply = nullptr;
259 }
260#endif
261
262 d->url = url;
263 d->sciurl = QUrl();
264 emit sourceChanged(d->url);
265
266 if (isComponentComplete())
267 load();
268}
269
270void QQuickBorderImage::load()
271{
272 Q_D(QQuickBorderImage);
273
274 if (d->url.isEmpty()) {
275 loadEmptyUrl();
276 } else {
277 if (d->url.path().endsWith(QLatin1String("sci"))) {
278 const QQmlContext *context = qmlContext(this);
279 QString lf = QQmlFile::urlToLocalFileOrQrc(context ? context->resolvedUrl(d->url)
280 : d->url);
281 if (!lf.isEmpty()) {
282 QFile file(lf);
283 if (!file.open(QIODevice::ReadOnly))
284 d->setStatus(Error);
285 else
286 setGridScaledImage(QQuickGridScaledImage(&file));
287 } else {
288#if QT_CONFIG(qml_network)
289 d->setProgress(0);
290 d->setStatus(Loading);
291
292 QNetworkRequest req(d->url);
293 d->sciReply = qmlEngine(this)->networkAccessManager()->get(req);
294 qmlobject_connect(d->sciReply, QNetworkReply, SIGNAL(finished()),
295 this, QQuickBorderImage, SLOT(sciRequestFinished()));
296#endif
297 }
298 } else {
299 loadPixmap(d->url, LoadPixmapOptions(HandleDPR | UseProviderOptions));
300 }
301 }
302}
303
304/*!
305 \qmlpropertygroup QtQuick::BorderImage::border
306 \qmlproperty int QtQuick::BorderImage::border.left
307 \qmlproperty int QtQuick::BorderImage::border.right
308 \qmlproperty int QtQuick::BorderImage::border.top
309 \qmlproperty int QtQuick::BorderImage::border.bottom
310
311 The 4 border lines (2 horizontal and 2 vertical) break the image into 9 sections,
312 as shown below:
313
314 \image declarative-scalegrid.png
315
316 Each border line (left, right, top, and bottom) specifies an offset in pixels
317 from the respective edge of the source image. By default, each border line has
318 a value of 0.
319
320 For example, the following definition sets the bottom line 10 pixels up from
321 the bottom of the image:
322
323 \qml
324 BorderImage {
325 border.bottom: 10
326 // ...
327 }
328 \endqml
329
330 The border lines can also be specified using a
331 \l {BorderImage::source}{.sci file}.
332*/
333
334QQuickScaleGrid *QQuickBorderImage::border()
335{
336 Q_D(QQuickBorderImage);
337 return d->getScaleGrid();
338}
339
340/*!
341 \qmlproperty enumeration QtQuick::BorderImage::horizontalTileMode
342 \qmlproperty enumeration QtQuick::BorderImage::verticalTileMode
343
344 This property describes how to repeat or stretch the middle parts of the border image.
345
346 \value BorderImage.Stretch Scales the image to fit to the available area.
347 \value BorderImage.Repeat Tile the image until there is no more space. May crop the last image.
348 \value BorderImage.Round Like Repeat, but scales the images down to ensure that the last image is not cropped.
349
350 The default tile mode for each property is BorderImage.Stretch.
351*/
352QQuickBorderImage::TileMode QQuickBorderImage::horizontalTileMode() const
353{
354 Q_D(const QQuickBorderImage);
355 return d->horizontalTileMode;
356}
357
358void QQuickBorderImage::setHorizontalTileMode(TileMode t)
359{
360 Q_D(QQuickBorderImage);
361 if (t != d->horizontalTileMode) {
362 d->horizontalTileMode = t;
363 emit horizontalTileModeChanged();
364 update();
365 }
366}
367
368QQuickBorderImage::TileMode QQuickBorderImage::verticalTileMode() const
369{
370 Q_D(const QQuickBorderImage);
371 return d->verticalTileMode;
372}
373
374void QQuickBorderImage::setVerticalTileMode(TileMode t)
375{
376 Q_D(QQuickBorderImage);
377 if (t != d->verticalTileMode) {
378 d->verticalTileMode = t;
379 emit verticalTileModeChanged();
380 update();
381 }
382}
383
384void QQuickBorderImage::setGridScaledImage(const QQuickGridScaledImage& sci)
385{
386 Q_D(QQuickBorderImage);
387 if (!sci.isValid()) {
388 d->setStatus(Error);
389 } else {
390 QQuickScaleGrid *sg = border();
391 sg->setTop(sci.gridTop());
392 sg->setBottom(sci.gridBottom());
393 sg->setLeft(sci.gridLeft());
394 sg->setRight(sci.gridRight());
395 d->horizontalTileMode = sci.horizontalTileRule();
396 d->verticalTileMode = sci.verticalTileRule();
397
398 d->sciurl = d->url.resolved(QUrl(sci.pixmapUrl()));
399 loadPixmap(d->sciurl);
400 }
401}
402
403void QQuickBorderImage::requestFinished()
404{
405 Q_D(QQuickBorderImage);
406
407 if (d->pendingPix != d->currentPix) {
408 std::swap(d->pendingPix, d->currentPix);
409 d->pendingPix->clear(this); // Clear the old image
410 }
411
412 const QSize impsize = d->currentPix->implicitSize();
413 setImplicitSize(impsize.width() / d->devicePixelRatio, impsize.height() / d->devicePixelRatio);
414
415 if (d->currentPix->isError()) {
416 qmlWarning(this) << d->currentPix->error();
417 d->setStatus(Error);
418 d->setProgress(0);
419 } else {
420 d->setStatus(Ready);
421 d->setProgress(1);
422 }
423
424 if (sourceSize() != d->oldSourceSize) {
425 d->oldSourceSize = sourceSize();
426 emit sourceSizeChanged();
427 }
428 if (d->frameCount != d->currentPix->frameCount()) {
429 d->frameCount = d->currentPix->frameCount();
430 emit frameCountChanged();
431 }
432
433 pixmapChange();
434}
435
436#if QT_CONFIG(qml_network)
437void QQuickBorderImage::sciRequestFinished()
438{
439 Q_D(QQuickBorderImage);
440
441 if (d->sciReply->error() != QNetworkReply::NoError) {
442 d->setStatus(Error);
443 d->sciReply->deleteLater();
444 d->sciReply = nullptr;
445 } else {
446 QQuickGridScaledImage sci(d->sciReply);
447 d->sciReply->deleteLater();
448 d->sciReply = nullptr;
449 setGridScaledImage(sci);
450 }
451}
452#endif // qml_network
453
454void QQuickBorderImage::doUpdate()
455{
456 update();
457}
458
459void QQuickBorderImagePrivate::calculateRects(const QQuickScaleGrid *border,
460 const QSize &sourceSize,
461 const QSizeF &targetSize,
462 int horizontalTileMode,
463 int verticalTileMode,
464 qreal devicePixelRatio,
465 QRectF *targetRect,
466 QRectF *innerTargetRect,
467 QRectF *innerSourceRect,
468 QRectF *subSourceRect)
469{
470 *innerSourceRect = QRectF(0, 0, 1, 1);
471 *targetRect = QRectF(0, 0, targetSize.width(), targetSize.height());
472 *innerTargetRect = *targetRect;
473
474 if (border) {
475 qreal borderLeft = border->left() * devicePixelRatio;
476 qreal borderRight = border->right() * devicePixelRatio;
477 qreal borderTop = border->top() * devicePixelRatio;
478 qreal borderBottom = border->bottom() * devicePixelRatio;
479 if (borderLeft + borderRight > sourceSize.width() && borderLeft < sourceSize.width())
480 borderRight = sourceSize.width() - borderLeft;
481 if (borderTop + borderBottom > sourceSize.height() && borderTop < sourceSize.height())
482 borderBottom = sourceSize.height() - borderTop;
483 *innerSourceRect = QRectF(QPointF(borderLeft / qreal(sourceSize.width()),
484 borderTop / qreal(sourceSize.height())),
485 QPointF((sourceSize.width() - borderRight) / qreal(sourceSize.width()),
486 (sourceSize.height() - borderBottom) / qreal(sourceSize.height()))),
487 *innerTargetRect = QRectF(border->left(),
488 border->top(),
489 qMax<qreal>(0, targetSize.width() - (border->right() + border->left())),
490 qMax<qreal>(0, targetSize.height() - (border->bottom() + border->top())));
491 }
492
493 qreal hTiles = 1;
494 qreal vTiles = 1;
495 const QSizeF innerTargetSize = innerTargetRect->size() * devicePixelRatio;
496 if (innerSourceRect->width() <= 0)
497 hTiles = 0;
498 else if (horizontalTileMode != QQuickBorderImage::Stretch) {
499 hTiles = innerTargetSize.width() / qreal(innerSourceRect->width() * sourceSize.width());
500 if (horizontalTileMode == QQuickBorderImage::Round)
501 hTiles = qCeil(hTiles);
502 }
503 if (innerSourceRect->height() <= 0)
504 vTiles = 0;
505 else if (verticalTileMode != QQuickBorderImage::Stretch) {
506 vTiles = innerTargetSize.height() / qreal(innerSourceRect->height() * sourceSize.height());
507 if (verticalTileMode == QQuickBorderImage::Round)
508 vTiles = qCeil(vTiles);
509 }
510
511 *subSourceRect = QRectF(0, 0, hTiles, vTiles);
512}
513
514
515QSGNode *QQuickBorderImage::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *)
516{
517 Q_D(QQuickBorderImage);
518
519 QSGTexture *texture = d->sceneGraphRenderContext()->textureForFactory(d->currentPix->textureFactory(), window());
520
521 if (!texture || width() <= 0 || height() <= 0) {
522 delete oldNode;
523 return nullptr;
524 }
525
526 QSGInternalImageNode *node = static_cast<QSGInternalImageNode *>(oldNode);
527
528 bool updatePixmap = d->pixmapChanged;
529 d->pixmapChanged = false;
530 if (!node) {
531 node = d->sceneGraphContext()->createInternalImageNode(d->sceneGraphRenderContext());
532 updatePixmap = true;
533 }
534
535 if (updatePixmap)
536 node->setTexture(texture);
537
538 // Don't implicitly create the scalegrid in the rendering thread...
539 QRectF targetRect;
540 QRectF innerTargetRect;
541 QRectF innerSourceRect;
542 QRectF subSourceRect;
543 d->calculateRects(d->border,
544 QSize(d->currentPix->width(), d->currentPix->height()), QSizeF(width(), height()),
545 d->horizontalTileMode, d->verticalTileMode, d->devicePixelRatio,
546 &targetRect, &innerTargetRect,
547 &innerSourceRect, &subSourceRect);
548
549 node->setTargetRect(targetRect);
550 node->setInnerSourceRect(innerSourceRect);
551 node->setInnerTargetRect(innerTargetRect);
552 node->setSubSourceRect(subSourceRect);
553 node->setMirror(d->mirrorHorizontally, d->mirrorVertically);
554
555 node->setMipmapFiltering(QSGTexture::None);
556 node->setFiltering(d->smooth ? QSGTexture::Linear : QSGTexture::Nearest);
557 if (innerSourceRect == QRectF(0, 0, 1, 1) && (subSourceRect.width() > 1 || subSourceRect.height() > 1)) {
558 node->setHorizontalWrapMode(QSGTexture::Repeat);
559 node->setVerticalWrapMode(QSGTexture::Repeat);
560 } else {
561 node->setHorizontalWrapMode(QSGTexture::ClampToEdge);
562 node->setVerticalWrapMode(QSGTexture::ClampToEdge);
563 }
564 node->setAntialiasing(d->antialiasing);
565 node->update();
566
567 return node;
568}
569
570void QQuickBorderImage::pixmapChange()
571{
572 Q_D(QQuickBorderImage);
573 d->pixmapChanged = true;
574 update();
575}
576
577/*!
578 \qmlproperty int QtQuick::BorderImage::currentFrame
579 \qmlproperty int QtQuick::BorderImage::frameCount
580 \since 5.14
581
582 currentFrame is the frame that is currently visible. The default is \c 0.
583 You can set it to a number between \c 0 and \c {frameCount - 1} to display a
584 different frame, if the image contains multiple frames.
585
586 frameCount is the number of frames in the image. Most images have only one frame.
587*/
588
589/*!
590 \qmlproperty bool QtQuick::BorderImage::retainWhileLoading
591 \since 6.8
592
593 \include qquickimage.cpp qml-image-retainwhileloading
594 */
595
596QT_END_NAMESPACE
597
598#include "moc_qquickborderimage_p.cpp"