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