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 "qsgsoftwareinternalimagenode_p.h"
41
42#include "qsgsoftwarepixmaptexture_p.h"
43#include "qsgsoftwarelayer_p.h"
44#include <QPainter>
45#include <qmath.h>
46
47QT_BEGIN_NAMESPACE
48
49namespace QSGSoftwareHelpers {
50// Helper from widgets/styles/qdrawutil.cpp
51
52static inline QMargins normalizedMargins(const QMargins &m)
53{
54 return QMargins(qMax(a: m.left(), b: 0), qMax(a: m.top(), b: 0), qMax(a: m.right(), b: 0), qMax(a: m.bottom(), b: 0));
55}
56
57void qDrawBorderPixmap(QPainter *painter, const QRect &targetRect, const QMargins &targetMarginsIn,
58 const QPixmap &pixmap, const QRect &sourceRect, const QMargins &sourceMarginsIn,
59 const QTileRules &rules, QDrawBorderPixmap::DrawingHints hints)
60{
61 QPainter::PixmapFragment d;
62 d.opacity = 1.0;
63 d.rotation = 0.0;
64
65 QPixmapFragmentsArray opaqueData;
66 QPixmapFragmentsArray translucentData;
67
68 QMargins sourceMargins = normalizedMargins(m: sourceMarginsIn);
69 QMargins targetMargins = normalizedMargins(m: targetMarginsIn);
70
71 const qreal sourceDpr = pixmap.devicePixelRatioF();
72 sourceMargins *= sourceDpr;
73
74 // source center
75 const int sourceCenterTop = sourceRect.top() + sourceMargins.top();
76 const int sourceCenterLeft = sourceRect.left() + sourceMargins.left();
77 const int sourceCenterBottom = sourceRect.bottom() - sourceMargins.bottom() + 1;
78 const int sourceCenterRight = sourceRect.right() - sourceMargins.right() + 1;
79 const int sourceCenterWidth = sourceCenterRight - sourceCenterLeft;
80 const int sourceCenterHeight = sourceCenterBottom - sourceCenterTop;
81 // target center
82 const int targetCenterTop = targetRect.top() + targetMargins.top();
83 const int targetCenterLeft = targetRect.left() + targetMargins.left();
84 const int targetCenterBottom = targetRect.bottom() - targetMargins.bottom() + 1;
85 const int targetCenterRight = targetRect.right() - targetMargins.right() + 1;
86 const int targetCenterWidth = targetCenterRight - targetCenterLeft;
87 const int targetCenterHeight = targetCenterBottom - targetCenterTop;
88
89 QVarLengthArray<qreal, 16> xTarget; // x-coordinates of target rectangles
90 QVarLengthArray<qreal, 16> yTarget; // y-coordinates of target rectangles
91
92 int columns = 3;
93 int rows = 3;
94 if (rules.horizontal != Qt::StretchTile && sourceCenterWidth != 0)
95 columns = qMax(a: 3, b: 2 + qCeil(v: (targetCenterWidth * sourceDpr) / qreal(sourceCenterWidth)));
96 if (rules.vertical != Qt::StretchTile && sourceCenterHeight != 0)
97 rows = qMax(a: 3, b: 2 + qCeil(v: (targetCenterHeight * sourceDpr) / qreal(sourceCenterHeight)));
98
99 xTarget.resize(asize: columns + 1);
100 yTarget.resize(asize: rows + 1);
101
102 xTarget[0] = targetRect.left();
103 xTarget[1] = targetCenterLeft;
104 xTarget[columns - 1] = targetCenterRight;
105 xTarget[columns] = targetRect.left() + targetRect.width();
106
107 yTarget[0] = targetRect.top();
108 yTarget[1] = targetCenterTop;
109 yTarget[rows - 1] = targetCenterBottom;
110 yTarget[rows] = targetRect.top() + targetRect.height();
111
112 qreal dx = targetCenterWidth;
113 qreal dy = targetCenterHeight;
114
115 switch (rules.horizontal) {
116 case Qt::StretchTile:
117 dx = targetCenterWidth;
118 break;
119 case Qt::RepeatTile:
120 dx = sourceCenterWidth / sourceDpr;
121 break;
122 case Qt::RoundTile:
123 dx = targetCenterWidth / qreal(columns - 2);
124 break;
125 }
126
127 for (int i = 2; i < columns - 1; ++i)
128 xTarget[i] = xTarget[i - 1] + dx;
129
130 switch (rules.vertical) {
131 case Qt::StretchTile:
132 dy = targetCenterHeight;
133 break;
134 case Qt::RepeatTile:
135 dy = sourceCenterHeight / sourceDpr;
136 break;
137 case Qt::RoundTile:
138 dy = targetCenterHeight / qreal(rows - 2);
139 break;
140 }
141
142 for (int i = 2; i < rows - 1; ++i)
143 yTarget[i] = yTarget[i - 1] + dy;
144
145 // corners
146 if (targetMargins.top() > 0 && targetMargins.left() > 0 && sourceMargins.top() > 0 && sourceMargins.left() > 0) { // top left
147 d.x = (0.5 * (xTarget[1] + xTarget[0]));
148 d.y = (0.5 * (yTarget[1] + yTarget[0]));
149 d.sourceLeft = sourceRect.left();
150 d.sourceTop = sourceRect.top();
151 d.width = sourceMargins.left();
152 d.height = sourceMargins.top();
153 d.scaleX = qreal(xTarget[1] - xTarget[0]) / d.width;
154 d.scaleY = qreal(yTarget[1] - yTarget[0]) / d.height;
155 if (hints & QDrawBorderPixmap::OpaqueTopLeft)
156 opaqueData.append(t: d);
157 else
158 translucentData.append(t: d);
159 }
160 if (targetMargins.top() > 0 && targetMargins.right() > 0 && sourceMargins.top() > 0 && sourceMargins.right() > 0) { // top right
161 d.x = (0.5 * (xTarget[columns] + xTarget[columns - 1]));
162 d.y = (0.5 * (yTarget[1] + yTarget[0]));
163 d.sourceLeft = sourceCenterRight;
164 d.sourceTop = sourceRect.top();
165 d.width = sourceMargins.right();
166 d.height = sourceMargins.top();
167 d.scaleX = qreal(xTarget[columns] - xTarget[columns - 1]) / d.width;
168 d.scaleY = qreal(yTarget[1] - yTarget[0]) / d.height;
169 if (hints & QDrawBorderPixmap::OpaqueTopRight)
170 opaqueData.append(t: d);
171 else
172 translucentData.append(t: d);
173 }
174 if (targetMargins.bottom() > 0 && targetMargins.left() > 0 && sourceMargins.bottom() > 0 && sourceMargins.left() > 0) { // bottom left
175 d.x = (0.5 * (xTarget[1] + xTarget[0]));
176 d.y =(0.5 * (yTarget[rows] + yTarget[rows - 1]));
177 d.sourceLeft = sourceRect.left();
178 d.sourceTop = sourceCenterBottom;
179 d.width = sourceMargins.left();
180 d.height = sourceMargins.bottom();
181 d.scaleX = qreal(xTarget[1] - xTarget[0]) / d.width;
182 d.scaleY = qreal(yTarget[rows] - yTarget[rows - 1]) / d.height;
183 if (hints & QDrawBorderPixmap::OpaqueBottomLeft)
184 opaqueData.append(t: d);
185 else
186 translucentData.append(t: d);
187 }
188 if (targetMargins.bottom() > 0 && targetMargins.right() > 0 && sourceMargins.bottom() > 0 && sourceMargins.right() > 0) { // bottom right
189 d.x = (0.5 * (xTarget[columns] + xTarget[columns - 1]));
190 d.y = (0.5 * (yTarget[rows] + yTarget[rows - 1]));
191 d.sourceLeft = sourceCenterRight;
192 d.sourceTop = sourceCenterBottom;
193 d.width = sourceMargins.right();
194 d.height = sourceMargins.bottom();
195 d.scaleX = qreal(xTarget[columns] - xTarget[columns - 1]) / d.width;
196 d.scaleY = qreal(yTarget[rows] - yTarget[rows - 1]) / d.height;
197 if (hints & QDrawBorderPixmap::OpaqueBottomRight)
198 opaqueData.append(t: d);
199 else
200 translucentData.append(t: d);
201 }
202
203 // horizontal edges
204 if (targetCenterWidth > 0 && sourceCenterWidth > 0) {
205 if (targetMargins.top() > 0 && sourceMargins.top() > 0) { // top
206 QPixmapFragmentsArray &data = hints & QDrawBorderPixmap::OpaqueTop ? opaqueData : translucentData;
207 d.sourceLeft = sourceCenterLeft;
208 d.sourceTop = sourceRect.top();
209 d.width = sourceCenterWidth;
210 d.height = sourceMargins.top();
211 d.y = (0.5 * (yTarget[1] + yTarget[0]));
212 d.scaleX = dx / d.width;
213 d.scaleY = qreal(yTarget[1] - yTarget[0]) / d.height;
214 for (int i = 1; i < columns - 1; ++i) {
215 d.x = (0.5 * (xTarget[i + 1] + xTarget[i]));
216 data.append(t: d);
217 }
218 if (rules.horizontal == Qt::RepeatTile)
219 data[data.size() - 1].width = ((xTarget[columns - 1] - xTarget[columns - 2]) / d.scaleX);
220 }
221 if (targetMargins.bottom() > 0 && sourceMargins.bottom() > 0) { // bottom
222 QPixmapFragmentsArray &data = hints & QDrawBorderPixmap::OpaqueBottom ? opaqueData : translucentData;
223 d.sourceLeft = sourceCenterLeft;
224 d.sourceTop = sourceCenterBottom;
225 d.width = sourceCenterWidth;
226 d.height = sourceMargins.bottom();
227 d.y = (0.5 * (yTarget[rows] + yTarget[rows - 1]));
228 d.scaleX = dx / d.width;
229 d.scaleY = qreal(yTarget[rows] - yTarget[rows - 1]) / d.height;
230 for (int i = 1; i < columns - 1; ++i) {
231 d.x = (0.5 * (xTarget[i + 1] + xTarget[i]));
232 data.append(t: d);
233 }
234 if (rules.horizontal == Qt::RepeatTile)
235 data[data.size() - 1].width = ((xTarget[columns - 1] - xTarget[columns - 2]) / d.scaleX);
236 }
237 }
238
239 // vertical edges
240 if (targetCenterHeight > 0 && sourceCenterHeight > 0) {
241 if (targetMargins.left() > 0 && sourceMargins.left() > 0) { // left
242 QPixmapFragmentsArray &data = hints & QDrawBorderPixmap::OpaqueLeft ? opaqueData : translucentData;
243 d.sourceLeft = sourceRect.left();
244 d.sourceTop = sourceCenterTop;
245 d.width = sourceMargins.left();
246 d.height = sourceCenterHeight;
247 d.x = (0.5 * (xTarget[1] + xTarget[0]));
248 d.scaleX = qreal(xTarget[1] - xTarget[0]) / d.width;
249 d.scaleY = dy / d.height;
250 for (int i = 1; i < rows - 1; ++i) {
251 d.y = (0.5 * (yTarget[i + 1] + yTarget[i]));
252 data.append(t: d);
253 }
254 if (rules.vertical == Qt::RepeatTile)
255 data[data.size() - 1].height = ((yTarget[rows - 1] - yTarget[rows - 2]) / d.scaleY);
256 }
257 if (targetMargins.right() > 0 && sourceMargins.right() > 0) { // right
258 QPixmapFragmentsArray &data = hints & QDrawBorderPixmap::OpaqueRight ? opaqueData : translucentData;
259 d.sourceLeft = sourceCenterRight;
260 d.sourceTop = sourceCenterTop;
261 d.width = sourceMargins.right();
262 d.height = sourceCenterHeight;
263 d.x = (0.5 * (xTarget[columns] + xTarget[columns - 1]));
264 d.scaleX = qreal(xTarget[columns] - xTarget[columns - 1]) / d.width;
265 d.scaleY = dy / d.height;
266 for (int i = 1; i < rows - 1; ++i) {
267 d.y = (0.5 * (yTarget[i + 1] + yTarget[i]));
268 data.append(t: d);
269 }
270 if (rules.vertical == Qt::RepeatTile)
271 data[data.size() - 1].height = ((yTarget[rows - 1] - yTarget[rows - 2]) / d.scaleY);
272 }
273 }
274
275 // center
276 if (targetCenterWidth > 0 && targetCenterHeight > 0 && sourceCenterWidth > 0 && sourceCenterHeight > 0) {
277 QPixmapFragmentsArray &data = hints & QDrawBorderPixmap::OpaqueCenter ? opaqueData : translucentData;
278 d.sourceLeft = sourceCenterLeft;
279 d.sourceTop = sourceCenterTop;
280 d.width = sourceCenterWidth;
281 d.height = sourceCenterHeight;
282 d.scaleX = dx / d.width;
283 d.scaleY = dy / d.height;
284
285 qreal repeatWidth = (xTarget[columns - 1] - xTarget[columns - 2]) / d.scaleX;
286 qreal repeatHeight = (yTarget[rows - 1] - yTarget[rows - 2]) / d.scaleY;
287
288 for (int j = 1; j < rows - 1; ++j) {
289 d.y = (0.5 * (yTarget[j + 1] + yTarget[j]));
290 for (int i = 1; i < columns - 1; ++i) {
291 d.x = (0.5 * (xTarget[i + 1] + xTarget[i]));
292 data.append(t: d);
293 }
294 if (rules.horizontal == Qt::RepeatTile)
295 data[data.size() - 1].width = repeatWidth;
296 }
297 if (rules.vertical == Qt::RepeatTile) {
298 for (int i = 1; i < columns - 1; ++i)
299 data[data.size() - i].height = repeatHeight;
300 }
301 }
302
303 if (opaqueData.size())
304 painter->drawPixmapFragments(fragments: opaqueData.data(), fragmentCount: opaqueData.size(), pixmap, hints: QPainter::OpaqueHint);
305 if (translucentData.size())
306 painter->drawPixmapFragments(fragments: translucentData.data(), fragmentCount: translucentData.size(), pixmap);
307}
308
309} // QSGSoftwareHelpers namespace
310
311QSGSoftwareInternalImageNode::QSGSoftwareInternalImageNode()
312 : m_innerSourceRect(0, 0, 1, 1)
313 , m_subSourceRect(0, 0, 1, 1)
314 , m_texture(nullptr)
315 , m_mirror(false)
316 , m_textureIsLayer(false)
317 , m_smooth(true)
318 , m_tileHorizontal(false)
319 , m_tileVertical(false)
320 , m_cachedMirroredPixmapIsDirty(false)
321{
322 setMaterial((QSGMaterial*)1);
323 setGeometry((QSGGeometry*)1);
324}
325
326
327void QSGSoftwareInternalImageNode::setTargetRect(const QRectF &rect)
328{
329 if (rect == m_targetRect)
330 return;
331 m_targetRect = rect;
332 markDirty(bits: DirtyGeometry);
333}
334
335void QSGSoftwareInternalImageNode::setInnerTargetRect(const QRectF &rect)
336{
337 if (rect == m_innerTargetRect)
338 return;
339 m_innerTargetRect = rect;
340 markDirty(bits: DirtyGeometry);
341}
342
343void QSGSoftwareInternalImageNode::setInnerSourceRect(const QRectF &rect)
344{
345 if (rect == m_innerSourceRect)
346 return;
347 m_innerSourceRect = rect;
348 markDirty(bits: DirtyGeometry);
349}
350
351void QSGSoftwareInternalImageNode::setSubSourceRect(const QRectF &rect)
352{
353 if (rect == m_subSourceRect)
354 return;
355 m_subSourceRect = rect;
356 markDirty(bits: DirtyGeometry);
357}
358
359void QSGSoftwareInternalImageNode::setTexture(QSGTexture *texture)
360{
361 m_texture = texture;
362 m_cachedMirroredPixmapIsDirty = true;
363 m_textureIsLayer = static_cast<bool>(qobject_cast<QSGSoftwareLayer*>(object: texture));
364 markDirty(bits: DirtyMaterial);
365}
366
367void QSGSoftwareInternalImageNode::setMirror(bool mirror)
368{
369 if (m_mirror != mirror) {
370 m_mirror = mirror;
371 m_cachedMirroredPixmapIsDirty = true;
372 markDirty(bits: DirtyMaterial);
373 }
374}
375
376void QSGSoftwareInternalImageNode::setMipmapFiltering(QSGTexture::Filtering /*filtering*/)
377{
378}
379
380void QSGSoftwareInternalImageNode::setFiltering(QSGTexture::Filtering filtering)
381{
382 bool smooth = (filtering == QSGTexture::Linear);
383 if (smooth == m_smooth)
384 return;
385
386 m_smooth = smooth;
387 markDirty(bits: DirtyMaterial);
388}
389
390void QSGSoftwareInternalImageNode::setHorizontalWrapMode(QSGTexture::WrapMode wrapMode)
391{
392 bool tileHorizontal = (wrapMode == QSGTexture::Repeat);
393 if (tileHorizontal == m_tileHorizontal)
394 return;
395
396 m_tileHorizontal = tileHorizontal;
397 markDirty(bits: DirtyMaterial);
398}
399
400void QSGSoftwareInternalImageNode::setVerticalWrapMode(QSGTexture::WrapMode wrapMode)
401{
402 bool tileVertical = (wrapMode == QSGTexture::Repeat);
403 if (tileVertical == m_tileVertical)
404 return;
405
406 m_tileVertical = (wrapMode == QSGTexture::Repeat);
407 markDirty(bits: DirtyMaterial);
408}
409
410void QSGSoftwareInternalImageNode::update()
411{
412 if (m_cachedMirroredPixmapIsDirty) {
413 if (m_mirror || m_textureIsLayer) {
414 QTransform transform(
415 (m_mirror ? -1 : 1), 0,
416 0 , (m_textureIsLayer ? -1 :1),
417 0 , 0
418 );
419 m_cachedMirroredPixmap = pixmap().transformed(transform);
420 } else {
421 //Cleanup cached pixmap if necessary
422 if (!m_cachedMirroredPixmap.isNull())
423 m_cachedMirroredPixmap = QPixmap();
424 }
425 m_cachedMirroredPixmapIsDirty = false;
426 }
427}
428
429void QSGSoftwareInternalImageNode::preprocess()
430{
431 bool doDirty = false;
432 QSGLayer *t = qobject_cast<QSGLayer *>(object: m_texture);
433 if (t) {
434 doDirty = t->updateTexture();
435 markDirty(bits: DirtyGeometry);
436 }
437 if (doDirty)
438 markDirty(bits: DirtyMaterial);
439 m_cachedMirroredPixmapIsDirty = doDirty;
440}
441
442static Qt::TileRule getTileRule(qreal factor)
443{
444 int ifactor = qRound(d: factor);
445 if (qFuzzyCompare(p1: factor, p2: ifactor )) {
446 if (ifactor == 1 || ifactor == 0)
447 return Qt::StretchTile;
448 return Qt::RoundTile;
449 }
450 return Qt::RepeatTile;
451}
452
453
454void QSGSoftwareInternalImageNode::paint(QPainter *painter)
455{
456 painter->setRenderHint(hint: QPainter::SmoothPixmapTransform, on: m_smooth);
457 // Disable antialiased clipping. It causes transformed tiles to have gaps.
458 painter->setRenderHint(hint: QPainter::Antialiasing, on: false);
459
460 const QPixmap &pm = m_mirror || m_textureIsLayer ? m_cachedMirroredPixmap : pixmap();
461
462 if (m_innerTargetRect != m_targetRect) {
463 // border image
464 QMargins margins(m_innerTargetRect.left() - m_targetRect.left(), m_innerTargetRect.top() - m_targetRect.top(),
465 m_targetRect.right() - m_innerTargetRect.right(), m_targetRect.bottom() - m_innerTargetRect.bottom());
466 QSGSoftwareHelpers::QTileRules tilerules(getTileRule(factor: m_subSourceRect.width()), getTileRule(factor: m_subSourceRect.height()));
467 QSGSoftwareHelpers::qDrawBorderPixmap(painter, targetRect: m_targetRect.toRect(), targetMarginsIn: margins, pixmap: pm, sourceRect: QRect(0, 0, pm.width(), pm.height()),
468 sourceMarginsIn: margins, rules: tilerules, hints: QSGSoftwareHelpers::QDrawBorderPixmap::DrawingHints{});
469 return;
470 }
471
472 if (m_tileHorizontal || m_tileVertical) {
473 painter->save();
474 qreal sx = m_targetRect.width()/(m_subSourceRect.width()*pm.width());
475 qreal sy = m_targetRect.height()/(m_subSourceRect.height()*pm.height());
476 painter->setTransform(transform: QTransform::fromScale(dx: sx, dy: sy), combine: true);
477 painter->drawTiledPixmap(rect: QRectF(m_targetRect.x()/sx, m_targetRect.y()/sy, m_targetRect.width()/sx, m_targetRect.height()/sy),
478 pm,
479 offset: QPointF(m_subSourceRect.left()*pm.width(), m_subSourceRect.top()*pm.height()));
480 painter->restore();
481 } else {
482 QRectF sr(m_subSourceRect.left()*pm.width(), m_subSourceRect.top()*pm.height(),
483 m_subSourceRect.width()*pm.width(), m_subSourceRect.height()*pm.height());
484 painter->drawPixmap(targetRect: m_targetRect, pixmap: pm, sourceRect: sr);
485 }
486}
487
488
489QRectF QSGSoftwareInternalImageNode::rect() const
490{
491 return m_targetRect;
492}
493
494const QPixmap &QSGSoftwareInternalImageNode::pixmap() const
495{
496 if (QSGSoftwarePixmapTexture *pt = qobject_cast<QSGSoftwarePixmapTexture*>(object: m_texture))
497 return pt->pixmap();
498 if (QSGSoftwareLayer *layer = qobject_cast<QSGSoftwareLayer*>(object: m_texture))
499 return layer->pixmap();
500 Q_ASSERT(m_texture == nullptr);
501 static const QPixmap nullPixmap;
502 return nullPixmap;
503}
504
505QT_END_NAMESPACE
506

source code of qtdeclarative/src/quick/scenegraph/adaptations/software/qsgsoftwareinternalimagenode.cpp