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 "qquickshadereffectmesh_p.h" |
5 | #include <QtQuick/qsggeometry.h> |
6 | #include "qquickshadereffect_p.h" |
7 | #include "qquickscalegrid_p_p.h" |
8 | #include "qquickborderimage_p_p.h" |
9 | #include <QtQuick/private/qsgbasicinternalimagenode_p.h> |
10 | |
11 | QT_BEGIN_NAMESPACE |
12 | |
13 | static const char qt_position_attribute_name[] = "qt_Vertex" ; |
14 | static const char qt_texcoord_attribute_name[] = "qt_MultiTexCoord0" ; |
15 | |
16 | const char *qtPositionAttributeName() |
17 | { |
18 | return qt_position_attribute_name; |
19 | } |
20 | |
21 | const char *qtTexCoordAttributeName() |
22 | { |
23 | return qt_texcoord_attribute_name; |
24 | } |
25 | |
26 | QQuickShaderEffectMesh::QQuickShaderEffectMesh(QObject *parent) |
27 | : QObject(parent) |
28 | { |
29 | } |
30 | |
31 | QQuickShaderEffectMesh::QQuickShaderEffectMesh(QObjectPrivate &dd, QObject *parent) |
32 | : QObject(dd, parent) |
33 | { |
34 | } |
35 | |
36 | /*! |
37 | \qmltype GridMesh |
38 | \instantiates QQuickGridMesh |
39 | \inqmlmodule QtQuick |
40 | \since 5.0 |
41 | \ingroup qtquick-effects |
42 | \brief Defines a mesh with vertices arranged in a grid. |
43 | |
44 | GridMesh defines a rectangular mesh consisting of vertices arranged in an |
45 | evenly spaced grid. It is used to generate \l{QSGGeometry}{geometry}. |
46 | The grid resolution is specified with the \l resolution property. |
47 | */ |
48 | |
49 | QQuickGridMesh::QQuickGridMesh(QObject *parent) |
50 | : QQuickShaderEffectMesh(parent) |
51 | , m_resolution(1, 1) |
52 | { |
53 | } |
54 | |
55 | bool QQuickGridMesh::validateAttributes(const QList<QByteArray> &attributes, int *posIndex) |
56 | { |
57 | const int attrCount = attributes.size(); |
58 | int positionIndex = attributes.indexOf(t: qtPositionAttributeName()); |
59 | int texCoordIndex = attributes.indexOf(t: qtTexCoordAttributeName()); |
60 | |
61 | switch (attrCount) { |
62 | case 0: |
63 | m_log = QLatin1String("Error: No attributes specified." ); |
64 | return false; |
65 | case 1: |
66 | if (positionIndex != 0) { |
67 | m_log = QLatin1String("Error: Missing \'" ) + QLatin1String(qtPositionAttributeName()) |
68 | + QLatin1String("\' attribute.\n" ); |
69 | return false; |
70 | } |
71 | break; |
72 | case 2: |
73 | if (positionIndex == -1 || texCoordIndex == -1) { |
74 | m_log.clear(); |
75 | if (positionIndex == -1) { |
76 | m_log = QLatin1String("Error: Missing \'" ) + QLatin1String(qtPositionAttributeName()) |
77 | + QLatin1String("\' attribute.\n" ); |
78 | } |
79 | if (texCoordIndex == -1) { |
80 | m_log += QLatin1String("Error: Missing \'" ) + QLatin1String(qtTexCoordAttributeName()) |
81 | + QLatin1String("\' attribute.\n" ); |
82 | } |
83 | return false; |
84 | } |
85 | break; |
86 | default: |
87 | m_log = QLatin1String("Error: Too many attributes specified." ); |
88 | return false; |
89 | } |
90 | |
91 | if (posIndex) |
92 | *posIndex = positionIndex; |
93 | |
94 | return true; |
95 | } |
96 | |
97 | QSGGeometry *QQuickGridMesh::updateGeometry(QSGGeometry *geometry, int attrCount, int posIndex, |
98 | const QRectF &srcRect, const QRectF &dstRect) |
99 | { |
100 | int vmesh = m_resolution.height(); |
101 | int hmesh = m_resolution.width(); |
102 | |
103 | if (!geometry) { |
104 | Q_ASSERT(attrCount == 1 || attrCount == 2); |
105 | geometry = new QSGGeometry(attrCount == 1 |
106 | ? QSGGeometry::defaultAttributes_Point2D() |
107 | : QSGGeometry::defaultAttributes_TexturedPoint2D(), |
108 | (vmesh + 1) * (hmesh + 1), vmesh * 2 * (hmesh + 2), |
109 | QSGGeometry::UnsignedShortType); |
110 | |
111 | } else { |
112 | geometry->allocate(vertexCount: (vmesh + 1) * (hmesh + 1), indexCount: vmesh * 2 * (hmesh + 2)); |
113 | } |
114 | |
115 | QSGGeometry::Point2D *vdata = static_cast<QSGGeometry::Point2D *>(geometry->vertexData()); |
116 | |
117 | for (int iy = 0; iy <= vmesh; ++iy) { |
118 | float fy = iy / float(vmesh); |
119 | float y = float(dstRect.top()) + fy * float(dstRect.height()); |
120 | float ty = float(srcRect.top()) + fy * float(srcRect.height()); |
121 | for (int ix = 0; ix <= hmesh; ++ix) { |
122 | float fx = ix / float(hmesh); |
123 | for (int ia = 0; ia < attrCount; ++ia) { |
124 | if (ia == posIndex) { |
125 | vdata->x = float(dstRect.left()) + fx * float(dstRect.width()); |
126 | vdata->y = y; |
127 | ++vdata; |
128 | } else { |
129 | vdata->x = float(srcRect.left()) + fx * float(srcRect.width()); |
130 | vdata->y = ty; |
131 | ++vdata; |
132 | } |
133 | } |
134 | } |
135 | } |
136 | |
137 | quint16 *indices = (quint16 *)geometry->indexDataAsUShort(); |
138 | int i = 0; |
139 | for (int iy = 0; iy < vmesh; ++iy) { |
140 | *(indices++) = i + hmesh + 1; |
141 | for (int ix = 0; ix <= hmesh; ++ix, ++i) { |
142 | *(indices++) = i + hmesh + 1; |
143 | *(indices++) = i; |
144 | } |
145 | *(indices++) = i - 1; |
146 | } |
147 | |
148 | return geometry; |
149 | } |
150 | |
151 | /*! |
152 | \qmlproperty size QtQuick::GridMesh::resolution |
153 | |
154 | This property holds the grid resolution. The resolution's width and height |
155 | specify the number of cells or spacings between vertices horizontally and |
156 | vertically respectively. The minimum and default is 1x1, which corresponds |
157 | to four vertices in total, one in each corner. |
158 | For non-linear vertex transformations, you probably want to set the |
159 | resolution higher. |
160 | |
161 | \table |
162 | \header |
163 | \li Result |
164 | \li QML code |
165 | \li gridmesh.vert |
166 | \row |
167 | \li \image declarative-gridmesh.png |
168 | \li \qml |
169 | import QtQuick 2.0 |
170 | |
171 | ShaderEffect { |
172 | width: 200 |
173 | height: 200 |
174 | mesh: GridMesh { |
175 | resolution: Qt.size(20, 20) |
176 | } |
177 | property variant source: Image { |
178 | source: "qt-logo.png" |
179 | sourceSize { width: 200; height: 200 } |
180 | } |
181 | vertexShader: "gridmesh.vert" |
182 | } |
183 | \endqml |
184 | \li \badcode |
185 | #version 440 |
186 | layout(location = 0) in vec4 qt_Vertex; |
187 | layout(location = 1) in vec2 qt_MultiTexCoord0; |
188 | layout(location = 0) out vec2 qt_TexCoord0; |
189 | layout(std140, binding = 0) uniform buf { |
190 | mat4 qt_Matrix; |
191 | float qt_Opacity; |
192 | float width; |
193 | }; |
194 | void main() { |
195 | vec4 pos = qt_Vertex; |
196 | float d = 0.5 * smoothstep(0.0, 1.0, qt_MultiTexCoord0.y); |
197 | pos.x = width * mix(d, 1.0 - d, qt_MultiTexCoord0.x); |
198 | gl_Position = qt_Matrix * pos; |
199 | qt_TexCoord0 = qt_MultiTexCoord0; |
200 | } |
201 | \endcode |
202 | \endtable |
203 | */ |
204 | |
205 | void QQuickGridMesh::setResolution(const QSize &res) |
206 | { |
207 | if (res == m_resolution) |
208 | return; |
209 | if (res.width() < 1 || res.height() < 1) { |
210 | return; |
211 | } |
212 | m_resolution = res; |
213 | emit resolutionChanged(); |
214 | emit geometryChanged(); |
215 | } |
216 | |
217 | QSize QQuickGridMesh::resolution() const |
218 | { |
219 | return m_resolution; |
220 | } |
221 | |
222 | /*! |
223 | \qmltype BorderImageMesh |
224 | \instantiates QQuickBorderImageMesh |
225 | \inqmlmodule QtQuick |
226 | \since 5.8 |
227 | \ingroup qtquick-effects |
228 | \brief Defines a mesh with vertices arranged like those of a BorderImage. |
229 | |
230 | BorderImageMesh provides BorderImage-like capabilities to a ShaderEffect |
231 | without the need for a potentially costly ShaderEffectSource. |
232 | |
233 | The following are functionally equivalent: |
234 | \qml |
235 | BorderImage { |
236 | id: borderImage |
237 | border { |
238 | left: 10 |
239 | right: 10 |
240 | top: 10 |
241 | bottom: 10 |
242 | } |
243 | source: "myImage.png" |
244 | visible: false |
245 | } |
246 | ShaderEffectSource { |
247 | id: effectSource |
248 | sourceItem: borderImage |
249 | visible: false |
250 | } |
251 | ShaderEffect { |
252 | property var source: effectSource |
253 | ... |
254 | } |
255 | \endqml |
256 | |
257 | \qml |
258 | Image { |
259 | id: image |
260 | source: "myImage.png" |
261 | visible: false |
262 | } |
263 | ShaderEffect { |
264 | property var source: image |
265 | mesh: BorderImageMesh { |
266 | border { |
267 | left: 10 |
268 | right: 10 |
269 | top: 10 |
270 | bottom: 10 |
271 | } |
272 | size: image.sourceSize |
273 | } |
274 | ... |
275 | } |
276 | \endqml |
277 | |
278 | But the BorderImageMesh version can typically be better optimized. |
279 | */ |
280 | QQuickBorderImageMesh::QQuickBorderImageMesh(QObject *parent) |
281 | : QQuickShaderEffectMesh(parent), m_border(new QQuickScaleGrid(this)), |
282 | m_horizontalTileMode(QQuickBorderImageMesh::Stretch), |
283 | m_verticalTileMode(QQuickBorderImageMesh::Stretch) |
284 | { |
285 | } |
286 | |
287 | bool QQuickBorderImageMesh::validateAttributes(const QList<QByteArray> &attributes, int *posIndex) |
288 | { |
289 | Q_UNUSED(attributes); |
290 | Q_UNUSED(posIndex); |
291 | return true; |
292 | } |
293 | |
294 | QSGGeometry *QQuickBorderImageMesh::updateGeometry(QSGGeometry *geometry, int attrCount, int posIndex, |
295 | const QRectF &srcRect, const QRectF &rect) |
296 | { |
297 | Q_UNUSED(attrCount); |
298 | Q_UNUSED(posIndex); |
299 | |
300 | QRectF innerSourceRect; |
301 | QRectF targetRect; |
302 | QRectF innerTargetRect; |
303 | QRectF subSourceRect; |
304 | |
305 | QQuickBorderImagePrivate::calculateRects(border: m_border, sourceSize: m_size, targetSize: rect.size(), horizontalTileMode: m_horizontalTileMode, verticalTileMode: m_verticalTileMode, |
306 | devicePixelRatio: 1, targetRect: &targetRect, innerTargetRect: &innerTargetRect, innerSourceRect: &innerSourceRect, subSourceRect: &subSourceRect); |
307 | |
308 | QRectF sourceRect = srcRect; |
309 | QRectF modifiedInnerSourceRect(sourceRect.x() + innerSourceRect.x() * sourceRect.width(), |
310 | sourceRect.y() + innerSourceRect.y() * sourceRect.height(), |
311 | innerSourceRect.width() * sourceRect.width(), |
312 | innerSourceRect.height() * sourceRect.height()); |
313 | |
314 | geometry = QSGBasicInternalImageNode::updateGeometry(targetRect, innerTargetRect, sourceRect, |
315 | innerSourceRect: modifiedInnerSourceRect, subSourceRect, geometry); |
316 | |
317 | return geometry; |
318 | } |
319 | |
320 | /*! |
321 | \qmlpropertygroup QtQuick::BorderImageMesh::border |
322 | \qmlproperty int QtQuick::BorderImageMesh::border.left |
323 | \qmlproperty int QtQuick::BorderImageMesh::border.right |
324 | \qmlproperty int QtQuick::BorderImageMesh::border.top |
325 | \qmlproperty int QtQuick::BorderImageMesh::border.bottom |
326 | |
327 | The 4 border lines (2 horizontal and 2 vertical) break the image into 9 sections, |
328 | as shown below: |
329 | |
330 | \image declarative-scalegrid.png |
331 | |
332 | Each border line (left, right, top, and bottom) specifies an offset in pixels |
333 | from the respective edge of the mesh. By default, each border line has |
334 | a value of 0. |
335 | |
336 | For example, the following definition sets the bottom line 10 pixels up from |
337 | the bottom of the mesh: |
338 | |
339 | \qml |
340 | BorderImageMesh { |
341 | border.bottom: 10 |
342 | // ... |
343 | } |
344 | \endqml |
345 | */ |
346 | QQuickScaleGrid *QQuickBorderImageMesh::border() const |
347 | { |
348 | return m_border; |
349 | } |
350 | |
351 | /*! |
352 | \qmlproperty size QtQuick::BorderImageMesh::size |
353 | |
354 | The base size of the mesh. This generally corresponds to the \l {Image::}{sourceSize} |
355 | of the image being used by the ShaderEffect. |
356 | */ |
357 | QSize QQuickBorderImageMesh::size() const |
358 | { |
359 | return m_size; |
360 | } |
361 | |
362 | void QQuickBorderImageMesh::setSize(const QSize &size) |
363 | { |
364 | if (size == m_size) |
365 | return; |
366 | m_size = size; |
367 | Q_EMIT sizeChanged(); |
368 | Q_EMIT geometryChanged(); |
369 | } |
370 | |
371 | /*! |
372 | \qmlproperty enumeration QtQuick::BorderImageMesh::horizontalTileMode |
373 | \qmlproperty enumeration QtQuick::BorderImageMesh::verticalTileMode |
374 | |
375 | This property describes how to repeat or stretch the middle parts of an image. |
376 | |
377 | \list |
378 | \li BorderImage.Stretch - Scales the image to fit to the available area. |
379 | \li BorderImage.Repeat - Tile the image until there is no more space. May crop the last image. |
380 | \li BorderImage.Round - Like Repeat, but scales the images down to ensure that the last image is not cropped. |
381 | \endlist |
382 | |
383 | The default tile mode for each property is BorderImage.Stretch. |
384 | */ |
385 | |
386 | QQuickBorderImageMesh::TileMode QQuickBorderImageMesh::horizontalTileMode() const |
387 | { |
388 | return m_horizontalTileMode; |
389 | } |
390 | |
391 | void QQuickBorderImageMesh::setHorizontalTileMode(TileMode t) |
392 | { |
393 | if (t == m_horizontalTileMode) |
394 | return; |
395 | m_horizontalTileMode = t; |
396 | Q_EMIT horizontalTileModeChanged(); |
397 | Q_EMIT geometryChanged(); |
398 | } |
399 | |
400 | QQuickBorderImageMesh::TileMode QQuickBorderImageMesh::verticalTileMode() const |
401 | { |
402 | return m_verticalTileMode; |
403 | } |
404 | |
405 | void QQuickBorderImageMesh::setVerticalTileMode(TileMode t) |
406 | { |
407 | if (t == m_verticalTileMode) |
408 | return; |
409 | |
410 | m_verticalTileMode = t; |
411 | Q_EMIT verticalTileModeChanged(); |
412 | Q_EMIT geometryChanged(); |
413 | } |
414 | |
415 | QT_END_NAMESPACE |
416 | |
417 | #include "moc_qquickshadereffectmesh_p.cpp" |
418 | |