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