Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
Loading...
Searching...
No Matches
qquickninepatchimage.cpp
Go to the documentation of this file.
1// Copyright (C) 2022 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
5
6#include <QtCore/qfileinfo.h>
7#include <QtQuick/qsggeometry.h>
8#include <QtQuick/qsgtexturematerial.h>
9#include <QtQuick/private/qsgnode_p.h>
10#include <QtQuick/private/qquickimage_p_p.h>
11
13
15{
16 QList<qreal> coordsForSize(qreal count) const;
17
18 inline bool isNull() const { return data.isEmpty(); }
19 inline int count() const { return data.size(); }
20 inline qreal at(int index) const { return data.at(index); }
21 inline qreal size() const { return data.last(); }
22
23 void fill(const QList<qreal> &coords, qreal count);
24 void clear();
25
26private:
27 bool inverted = false;
28 QList<qreal> data;
29};
30
32{
33 // n = number of stretchable sections
34 // We have to compensate when adding 0 and/or
35 // the source image width to the divs vector.
36 const int l = data.size();
37 const int n = (inverted ? l - 1 : l) / 2;
38 const qreal stretch = (size - data.last()) / n;
39
40 QList<qreal> coords;
41 coords.reserve(l);
42 coords.append(0);
43
44 bool stretched = !inverted;
45 for (int i = 1; i < l; ++i) {
46 qreal advance = data[i] - data[i - 1];
47 if (stretched)
48 advance += stretch;
49 coords.append(coords.last() + advance);
50
51 stretched = !stretched;
52 }
53
54 return coords;
55}
56
57/*
58 Adds the 0 index coordinate if appropriate, and the one at "size".
59*/
60void QQuickNinePatchData::fill(const QList<qreal> &coords, qreal size)
61{
62 data.clear();
63 inverted = coords.isEmpty() || coords.first() != 0;
64
65 // Reserve an extra item in case we need to add the image width/height
66 if (inverted) {
67 data.reserve(coords.size() + 2);
68 data.append(0);
69 } else {
70 data.reserve(coords.size() + 1);
71 }
72
73 data += coords;
74 data.append(size);
75}
76
78{
79 data.clear();
80}
81
83{
84public:
87
88 void initialize(QSGTexture *texture, const QSizeF &targetSize, const QSize &sourceSize,
89 const QQuickNinePatchData &xDivs, const QQuickNinePatchData &yDivs, qreal dpr);
90
91private:
92 QSGGeometry m_geometry;
93 QSGTextureMaterial m_material;
94};
95
97 : m_geometry(QSGGeometry::defaultAttributes_TexturedPoint2D(), 4)
98{
100 setGeometry(&m_geometry);
101 setMaterial(&m_material);
102}
103
105{
106 delete m_material.texture();
107}
108
109void QQuickNinePatchNode::initialize(QSGTexture *texture, const QSizeF &targetSize, const QSize &sourceSize,
110 const QQuickNinePatchData &xDivs, const QQuickNinePatchData &yDivs, qreal dpr)
111{
112 delete m_material.texture();
113 m_material.setTexture(texture);
114
115 const int xlen = xDivs.count();
116 const int ylen = yDivs.count();
117
118 if (xlen > 0 && ylen > 0) {
119 const int quads = (xlen - 1) * (ylen - 1);
120 static const int verticesPerQuad = 6;
121 m_geometry.allocate(xlen * ylen, verticesPerQuad * quads);
122
124 QList<qreal> xCoords = xDivs.coordsForSize(targetSize.width());
125 QList<qreal> yCoords = yDivs.coordsForSize(targetSize.height());
126
127 for (int y = 0; y < ylen; ++y) {
128 for (int x = 0; x < xlen; ++x, ++vertices)
129 vertices->set(xCoords[x] / dpr, yCoords[y] / dpr,
130 xDivs.at(x) / sourceSize.width(),
131 yDivs.at(y) / sourceSize.height());
132 }
133
134 quint16 *indices = m_geometry.indexDataAsUShort();
135 int n = quads;
136 for (int q = 0; n--; ++q) {
137 if ((q + 1) % xlen == 0) // next row
138 ++q;
139 // Bottom-left half quad triangle
140 indices[0] = q;
141 indices[1] = q + xlen;
142 indices[2] = q + xlen + 1;
143
144 // Top-right half quad triangle
145 indices[3] = q;
146 indices[4] = q + xlen + 1;
147 indices[5] = q + 1;
148
149 indices += verticesPerQuad;
150 }
151 }
152
154}
155
157{
158 Q_DECLARE_PUBLIC(QQuickNinePatchImage)
159
160public:
161 void updatePatches();
162 void updatePaddings(const QSizeF &size, const QList<qreal> &horizontal, const QList<qreal> &vertical);
163 void updateInsets(const QList<qreal> &horizontal, const QList<qreal> &vertical);
164
165 bool resetNode = false;
174
178};
179
180/*
181 Examines each pixel in a horizontal or vertical (if offset is equal to the image's width)
182 line, storing the start and end index ("coordinate") of each 9-patch line.
183
184 For instance, in the 7x3 (9x5 actual size) 9-patch image below, which has no horizontal
185 stretchable area, it would return {}:
186
187 +-----+
188 | |
189 +-----+
190
191 If indices 3 to 5 were marked, it would return {2, 5}:
192
193 xxx
194 +-----+
195 | |
196 +-----+
197
198 If indices 3 and 5 were marked, it would store {0, 2, 3, 4, 5, 7}:
199
200 x x
201 +-----+
202 | |
203 +-----+
204*/
205static QList<qreal> readCoords(const QRgb *data, int from, int count, int offset, QRgb color)
206{
207 int p1 = -1;
208 QList<qreal> coords;
209 for (int i = 0; i < count; ++i) {
210 int p2 = from + i * offset;
211 if (data[p2] == color) {
212 // colored pixel
213 if (p1 == -1) {
214 // This is the start of a 9-patch line.
215 p1 = i;
216 }
217 } else {
218 // empty pixel
219 if (p1 != -1) {
220 // This is the end of a 9-patch line; add the start and end indices as coordinates...
221 coords << p1 << i;
222 // ... and reset p1 so that we can search for the next one.
223 p1 = -1;
224 }
225 }
226 }
227 return coords;
228}
229
230/*
231 Called whenever a 9-patch image is set as the image's source.
232
233 Reads the 9-patch lines from the source image and sets the
234 inset and padding properties accordingly.
235*/
237{
238 if (ninePatch.isNull())
239 return;
240
241 int w = ninePatch.width();
242 int h = ninePatch.height();
243 const QRgb *data = reinterpret_cast<const QRgb *>(ninePatch.constBits());
244
245 const QRgb black = qRgb(0,0,0);
246 const QRgb red = qRgb(255,0,0);
247
248 xDivs.fill(readCoords(data, 1, w - 1, 1, black), w - 2); // top left -> top right
249 yDivs.fill(readCoords(data, w, h - 1, w, black), h - 2); // top left -> bottom left
250
251 QList<qreal> hInsets = readCoords(data, (h - 1) * w + 1, w - 1, 1, red); // bottom left -> bottom right
252 QList<qreal> vInsets = readCoords(data, 2 * w - 1, h - 1, w, red); // top right -> bottom right
253 updateInsets(hInsets, vInsets);
254
255 const QSizeF sz(w - leftInset - rightInset, h - topInset - bottomInset);
256 QList<qreal> hPaddings = readCoords(data, (h - 1) * w + leftInset + 1, sz.width() - 2, 1, black); // bottom left -> bottom right
257 QList<qreal> vPaddings = readCoords(data, (2 + topInset) * w - 1, sz.height() - 2, w, black); // top right -> bottom right
258 updatePaddings(sz, hPaddings, vPaddings);
259}
260
261void QQuickNinePatchImagePrivate::updatePaddings(const QSizeF &size, const QList<qreal> &horizontal, const QList<qreal> &vertical)
262{
264 qreal oldTopPadding = topPadding;
265 qreal oldLeftPadding = leftPadding;
266 qreal oldRightPadding = rightPadding;
267 qreal oldBottomPadding = bottomPadding;
268
269 if (horizontal.size() >= 2) {
270 leftPadding = horizontal.first();
271 rightPadding = size.width() - horizontal.last() - 2;
272 } else {
273 leftPadding = 0;
274 rightPadding = 0;
275 }
276
277 if (vertical.size() >= 2) {
278 topPadding = vertical.first();
279 bottomPadding = size.height() - vertical.last() - 2;
280 } else {
281 topPadding = 0;
282 bottomPadding = 0;
283 }
284
285 if (!qFuzzyCompare(oldTopPadding, topPadding))
286 emit q->topPaddingChanged();
287 if (!qFuzzyCompare(oldBottomPadding, bottomPadding))
288 emit q->bottomPaddingChanged();
289 if (!qFuzzyCompare(oldLeftPadding, leftPadding))
290 emit q->leftPaddingChanged();
291 if (!qFuzzyCompare(oldRightPadding, rightPadding))
292 emit q->rightPaddingChanged();
293}
294
295void QQuickNinePatchImagePrivate::updateInsets(const QList<qreal> &horizontal, const QList<qreal> &vertical)
296{
298 qreal oldTopInset = topInset;
299 qreal oldLeftInset = leftInset;
300 qreal oldRightInset = rightInset;
301 qreal oldBottomInset = bottomInset;
302
303 if (horizontal.size() >= 2 && horizontal.first() == 0)
304 leftInset = horizontal.at(1);
305 else
306 leftInset = 0;
307
308 if (horizontal.size() == 2 && horizontal.first() > 0)
309 rightInset = horizontal.last() - horizontal.first();
310 else if (horizontal.size() == 4)
311 rightInset = horizontal.last() - horizontal.at(2);
312 else
313 rightInset = 0;
314
315 if (vertical.size() >= 2 && vertical.first() == 0)
316 topInset = vertical.at(1);
317 else
318 topInset = 0;
319
320 if (vertical.size() == 2 && vertical.first() > 0)
321 bottomInset = vertical.last() - vertical.first();
322 else if (vertical.size() == 4)
323 bottomInset = vertical.last() - vertical.at(2);
324 else
325 bottomInset = 0;
326
327 if (!qFuzzyCompare(oldTopInset, topInset))
328 emit q->topInsetChanged();
329 if (!qFuzzyCompare(oldBottomInset, bottomInset))
330 emit q->bottomInsetChanged();
331 if (!qFuzzyCompare(oldLeftInset, leftInset))
332 emit q->leftInsetChanged();
333 if (!qFuzzyCompare(oldRightInset, rightInset))
334 emit q->rightInsetChanged();
335}
336
339{
341 d->smooth = qEnvironmentVariableIntValue("QT_QUICK_CONTROLS_IMAGINE_SMOOTH");
342}
343
345{
346 Q_D(const QQuickNinePatchImage);
347 return d->topPadding / d->devicePixelRatio;
348}
349
351{
352 Q_D(const QQuickNinePatchImage);
353 return d->leftPadding / d->devicePixelRatio;
354}
355
357{
358 Q_D(const QQuickNinePatchImage);
359 return d->rightPadding / d->devicePixelRatio;
360}
361
363{
364 Q_D(const QQuickNinePatchImage);
365 return d->bottomPadding / d->devicePixelRatio;
366}
367
369{
370 Q_D(const QQuickNinePatchImage);
371 return d->topInset / d->devicePixelRatio;
372}
373
375{
376 Q_D(const QQuickNinePatchImage);
377 return d->leftInset / d->devicePixelRatio;
378}
379
381{
382 Q_D(const QQuickNinePatchImage);
383 return d->rightInset / d->devicePixelRatio;
384}
385
387{
388 Q_D(const QQuickNinePatchImage);
389 return d->bottomInset / d->devicePixelRatio;
390}
391
393{
395 if (QFileInfo(d->url.fileName()).completeSuffix().toLower() == QLatin1String("9.png")) {
396 // Keep resetNode if it is already set, we do not want to miss an
397 // ImageNode->NinePatchNode change. Without this there's a chance one gets
398 // an incorrect cast on oldNode every once in a while with source changes.
399 if (!d->resetNode)
400 d->resetNode = d->ninePatch.isNull();
401
402 d->ninePatch = d->currentPix->image();
403 if (d->ninePatch.depth() != 32)
404 d->ninePatch = std::move(d->ninePatch).convertToFormat(QImage::Format_ARGB32);
405
406 int w = d->ninePatch.width();
407 int h = d->ninePatch.height();
408 d->currentPix->setImage(QImage(d->ninePatch.constBits() + 4 * (w + 1), w - 2, h - 2, d->ninePatch.bytesPerLine(), d->ninePatch.format()));
409
410 d->updatePatches();
411 } else {
412 /*
413 Only change resetNode when it's false; i.e. when no reset is pending.
414 updatePaintNode() will take care of setting it to false if it's true.
415
416 Consider the following changes in source:
417
418 normal.png => press.9.png => normal.png => focus.png
419
420 If the last two events happen quickly, pixmapChange() can be called
421 twice with no call to updatePaintNode() inbetween. On the first call,
422 resetNode will be true (because ninePatch is not null since it is still
423 in the process of going from a 9-patch image to a regular image),
424 and on the second call, resetNode would be false if we didn't have this check.
425 This results in the oldNode never being deleted, and QQuickImage
426 tries to static_cast a QQuickNinePatchImage to a QSGInternalImageNode.
427 */
428 if (!d->resetNode)
429 d->resetNode = !d->ninePatch.isNull();
430 d->ninePatch = QImage();
431 }
433}
434
436{
438 Q_UNUSED(data);
439
440 if (d->resetNode) {
441 delete oldNode;
442 oldNode = nullptr;
443 d->resetNode = false;
444 }
445
446 if (d->ninePatch.isNull())
447 return QQuickImage::updatePaintNode(oldNode, data);
448
449 QSizeF sz = size();
450 QImage image = d->currentPix->image();
451 if (!sz.isValid() || image.isNull()) {
452 if (d->provider)
453 d->provider->updateTexture(nullptr);
454 delete oldNode;
455 return nullptr;
456 }
457
458 QQuickNinePatchNode *patchNode = static_cast<QQuickNinePatchNode *>(oldNode);
459 if (!patchNode)
460 patchNode = new QQuickNinePatchNode;
461
462#ifdef QSG_RUNTIME_DESCRIPTION
463 qsgnode_set_description(patchNode, QString::fromLatin1("QQuickNinePatchImage: '%1'").arg(d->url.toString()));
464#endif
465
466 // The image may wrap non-owned data (due to pixmapChange). Ensure we never
467 // pass such an image to the scenegraph, because with a separate render
468 // thread the data may become invalid (in a subsequent pixmapChange on the
469 // gui thread) by the time the renderer gets to do something with the QImage
470 // passed in here.
471 image.detach();
472
473 QSGTexture *texture = window()->createTextureFromImage(image);
474 patchNode->initialize(texture, sz * d->devicePixelRatio, image.size(), d->xDivs, d->yDivs, d->devicePixelRatio);
475 auto patchNodeMaterial = static_cast<QSGTextureMaterial *>(patchNode->material());
476 patchNodeMaterial->setFiltering(d->smooth ? QSGTexture::Linear : QSGTexture::Nearest);
477 return patchNode;
478}
479
481
482#include "moc_qquickninepatchimage_p.cpp"
QString completeSuffix() const
Returns the complete suffix (extension) of the file.
\inmodule QtGui
Definition qimage.h:37
int width() const
Returns the width of the image.
bool isNull() const
Returns true if it is a null image, otherwise returns false.
Definition qimage.cpp:1222
int height() const
Returns the height of the image.
@ Format_ARGB32
Definition qimage.h:47
const uchar * constBits() const
Returns a pointer to the first pixel data.
Definition qimage.cpp:1733
qsizetype size() const noexcept
Definition qlist.h:397
QSGNode * updatePaintNode(QSGNode *, UpdatePaintNodeData *) override
Called on the render thread when it is time to sync the state of the item with the scene graph.
void pixmapChange() override
The QQuickItem class provides the most basic of all visual items in \l {Qt Quick}.
Definition qquickitem.h:63
void updatePaddings(const QSizeF &size, const QList< qreal > &horizontal, const QList< qreal > &vertical)
void updateInsets(const QList< qreal > &horizontal, const QList< qreal > &vertical)
QQuickNinePatchImage(QQuickItem *parent=nullptr)
QSGNode * updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *data) override
Called on the render thread when it is time to sync the state of the item with the scene graph.
void initialize(QSGTexture *texture, const QSizeF &targetSize, const QSize &sourceSize, const QQuickNinePatchData &xDivs, const QQuickNinePatchData &yDivs, qreal dpr)
void setGeometry(QSGGeometry *geometry)
Sets the geometry of this node to geometry.
Definition qsgnode.cpp:764
The QSGGeometryNode class is used for all rendered content in the scene graph.
Definition qsgnode.h:188
void setMaterial(QSGMaterial *material)
Sets the material of this geometry node to material.
Definition qsgnode.cpp:927
The QSGGeometry class provides low-level storage for graphics primitives in the \l{Qt Quick Scene Gra...
Definition qsggeometry.h:15
void setDrawingMode(unsigned int mode)
Sets the mode to be used for drawing this geometry.
TexturedPoint2D * vertexDataAsTexturedPoint2D()
Convenience function to access the vertex data as a mutable array of QSGGeometry::TexturedPoint2D.
void allocate(int vertexCount, int indexCount=0)
Resizes the vertex and index data of this geometry object to fit vertexCount vertices and indexCount ...
quint16 * indexDataAsUShort()
Convenience function to access the index data as a mutable array of 16-bit unsigned integers.
\group qtquick-scenegraph-nodes \title Qt Quick Scene Graph Node classes
Definition qsgnode.h:37
@ DirtyMaterial
Definition qsgnode.h:75
@ DirtyGeometry
Definition qsgnode.h:74
void markDirty(DirtyState bits)
Notifies all connected renderers that the node has dirty bits.
Definition qsgnode.cpp:624
void setFiltering(QSGTexture::Filtering filteringType)
Sets the filtering to filtering.
void setTexture(QSGTexture *texture)
Sets the texture of this material to texture.
QSGTexture * texture() const
Returns this texture material's texture.
The QSGTextureMaterial class provides a convenient way of rendering textured geometry in the scene gr...
\inmodule QtQuick
Definition qsgtexture.h:20
\inmodule QtCore
Definition qsize.h:208
constexpr bool isValid() const noexcept
Returns true if both the width and height are equal to or greater than 0; otherwise returns false.
Definition qsize.h:329
constexpr qreal width() const noexcept
Returns the width.
Definition qsize.h:332
constexpr qreal height() const noexcept
Returns the height.
Definition qsize.h:335
\inmodule QtCore
Definition qsize.h:25
constexpr int height() const noexcept
Returns the height.
Definition qsize.h:133
constexpr int width() const noexcept
Returns the width.
Definition qsize.h:130
static QString fromLatin1(QByteArrayView ba)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qstring.cpp:5871
QString toLower() const &
Definition qstring.h:435
QPixmap p2
QPixmap p1
[0]
Combined button and popup list for selecting options.
Definition image.cpp:4
static bool initialize()
Definition qctf.cpp:94
bool qFuzzyCompare(qfloat16 p1, qfloat16 p2) noexcept
Definition qfloat16.h:333
GLint GLint GLint GLint GLint x
[0]
GLfloat GLfloat GLfloat w
[0]
GLenum GLuint GLintptr GLsizeiptr size
[1]
GLuint index
[2]
GLsizei const GLubyte GLsizei GLenum const void * coords
GLenum GLenum GLsizei count
GLint GLsizei GLsizei GLenum GLenum GLsizei void * data
GLuint color
[2]
GLenum GLuint texture
GLenum GLuint GLintptr offset
GLfloat n
GLsizei GLenum const void * indices
GLint y
GLfloat GLfloat GLfloat GLfloat h
GLdouble GLdouble GLdouble GLdouble q
Definition qopenglext.h:259
static QT_BEGIN_NAMESPACE qreal dpr(const QWindow *w)
static QList< qreal > readCoords(const QRgb *data, int from, int count, int offset, QRgb color)
QT_BEGIN_NAMESPACE typedef unsigned int QRgb
Definition qrgb.h:13
constexpr QRgb qRgb(int r, int g, int b)
Definition qrgb.h:30
void qsgnode_set_description(QSGNode *node, const QString &description)
Definition qsgnode.cpp:641
SSL_CTX int void * arg
Q_CORE_EXPORT int qEnvironmentVariableIntValue(const char *varName, bool *ok=nullptr) noexcept
#define emit
#define Q_UNUSED(x)
unsigned short quint16
Definition qtypes.h:48
double qreal
Definition qtypes.h:187
aWidget window() -> setWindowTitle("New Window Title")
[2]
QGraphicsSvgItem * red
QGraphicsSvgItem * black
QList< qreal > coordsForSize(qreal count) const
void fill(const QList< qreal > &coords, qreal count)
qreal at(int index) const
The QSGGeometry::TexturedPoint2D struct is a convenience struct for accessing 2D Points with texture ...
Definition qsggeometry.h:85
void set(float nx, float ny, float ntx, float nty)
Sets the position of the vertex to x and y and the texture coordinate to tx and ty.
Definition qsggeometry.h:88