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

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