Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qquickitemgenerator.cpp
Go to the documentation of this file.
1// Copyright (C) 2024 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
6#include <QtQuickVectorImageHelpers/private/qquickitemspy_p.h>
7
8#include <private/qquickitem_p.h>
9#include <private/qquicktranslate_p.h>
10#include <private/qquickshape_p.h>
11#include <private/qquickpath_p.h>
12#include <private/qquickimage_p.h>
13#include <private/qquicktext_p.h>
14#include <private/qquickrectangle_p.h>
15#include <private/qquickshadereffect_p.h>
16#include <private/qquickshadereffectsource_p.h>
17#include <private/qquickmultieffect_p.h>
18
19#include "utils_p.h"
20
21#include <QtCore/qdir.h>
22#include <QtCore/qfileinfo.h>
23#include <QtCore/qloggingcategory.h>
24#include <QtGui/qfontmetrics.h>
25#include <QtQml/qqmlcontext.h>
26#include <QtQml/qqmlengine.h>
27#include <QtQml/qqmlparserstatus.h>
28
30
31using namespace Qt::StringLiterals;
32
33QQuickItemGenerator::QQuickItemGenerator(const QString &fileName,
34 QQuickVectorImageGenerator::GeneratorFlags flags,
35 QQmlContext *context)
36 : QQuickGenerator(fileName, flags), m_context(context)
37{
38}
39
40QQuickItemGenerator::~QQuickItemGenerator() = default;
41
42QQuickItem *QQuickItemGenerator::takeRootItem()
43{
44 QQuickItem *item = m_rootItem;
45 m_rootItem = nullptr;
46 return item;
47}
48
49QList<std::function<void()>> *QQuickItemGenerator::activeRecord() const
50{
51 if (m_currentDefsRecord)
52 return m_currentDefsRecord;
53 return m_currentMarkerRecord;
54}
55
56QQuickShape *QQuickItemGenerator::createShapeContainer()
57{
58 auto *shape = new QQuickShape;
59 if (m_flags.testFlag(QQuickVectorImageGenerator::GeneratorFlag::CurveRenderer))
60 shape->setPreferredRendererType(QQuickShape::CurveRenderer);
61 if (m_flags.testFlag(QQuickVectorImageGenerator::GeneratorFlag::AsyncShapes))
62 shape->setAsynchronous(true);
63 return shape;
64}
65
66void QQuickItemGenerator::pushItem(QQuickItem *item)
67{
68 if (!m_itemStack.isEmpty()) {
69 item->setParent(m_itemStack.top());
70 item->setParentItem(m_itemStack.top());
71 }
72 m_itemStack.push(item);
73}
74
75QQuickItem *QQuickItemGenerator::popItem()
76{
77 return m_itemStack.isEmpty() ? nullptr : m_itemStack.pop();
78}
79
80QQuickItem *QQuickItemGenerator::currentItem() const
81{
82 return m_itemStack.isEmpty() ? nullptr : m_itemStack.top();
83}
84
85QString QQuickItemGenerator::generateNodeBase(const NodeInfo &info, const QString &idSuffix)
86{
87 Q_UNUSED(idSuffix)
88
89 static qint64 maxNodes =
90 qEnvironmentVariableIntegerValue("QT_QUICKVECTORIMAGE_MAX_NODES").value_or(10000);
91 if (Q_UNLIKELY(!checkSanityLimit(++m_nodeCounter, maxNodes, "nodes"_L1)))
92 return {};
93
94 QQuickItem *item = currentItem();
95 if (!item)
96 return info.id;
97
98 if (!info.nodeId.isEmpty())
99 item->setObjectName(info.nodeId);
100
101 item->setTransformOrigin(QQuickItem::TopLeft);
102
103 if (!info.bounds.isNull()) {
104 item->setWidth(info.bounds.width());
105 item->setHeight(info.bounds.height());
106 }
107
108 if (info.filterId.isEmpty() && info.maskId.isEmpty()) {
109 if (!info.isDefaultOpacity)
110 item->setOpacity(info.opacity.defaultValue().toReal());
111 }
112
113 if (!info.isDefaultTransform) {
114 QTransform xf = info.transform.defaultValue().value<QTransform>();
115 auto *matrix = new QQuickMatrix4x4(item);
116 matrix->setMatrix(QMatrix4x4(xf));
117 auto xformProp = item->transform();
118 xformProp.append(&xformProp, matrix);
119 }
120
121 return info.id;
122}
123
124bool QQuickItemGenerator::generateRootNode(const StructureNodeInfo &info)
125{
126 if (Q_UNLIKELY(errorState()))
127 return false;
128
129 if (info.stage == StructureNodeStage::Start) {
130 auto *root = new QQuickItem;
131 if (info.size.width() > 0)
132 root->setImplicitWidth(info.size.width());
133 if (info.size.height() > 0)
134 root->setImplicitHeight(info.size.height());
135 m_rootItem = root;
136 m_containerSize = info.viewBox.isEmpty() ? QSizeF(info.size) : info.viewBox.size();
137
138 if (!isNodeVisible(info))
139 return false;
140
141 if (!info.viewBox.isEmpty() && info.size.width() > 0 && info.size.height() > 0) {
142 auto xformProp = root->transform();
143 if (!qFuzzyIsNull(info.viewBox.x()) || !qFuzzyIsNull(info.viewBox.y())) {
144 auto *translate = new QQuickTranslate(root);
145 translate->setX(-info.viewBox.x());
146 translate->setY(-info.viewBox.y());
147 xformProp.append(&xformProp, translate);
148 }
149 auto *scale = new QQuickScale(root);
150 scale->setXScale(info.size.width() / info.viewBox.width());
151 scale->setYScale(info.size.height() / info.viewBox.height());
152 xformProp.append(&xformProp, scale);
153 }
154
155 m_topLevelScaleSpy = new QQuickItemSpy(root);
156 m_topLevelScaleSpy->setWidth(1);
157 m_topLevelScaleSpy->setHeight(1);
158 m_topLevelScaleSpy->setVisible(false);
159
160 pushItem(root);
161 generateNodeBase(info);
162 } else {
163 popItem();
164 }
165
166 return true;
167}
168
169bool QQuickItemGenerator::generateStructureNode(const StructureNodeInfo &info)
170{
171 if (Q_UNLIKELY(errorState()))
172 return false;
173
174 if (auto *rec = activeRecord()) {
175 rec->append([this, info]() { generateStructureNode(info); });
176 return true;
177 }
178
179 if (!isNodeVisible(info))
180 return false;
181
182 if (info.stage == StructureNodeStage::Start) {
183 QQuickItem *item;
184 if (!info.forceSeparatePaths && info.isPathContainer) {
185 item = createShapeContainer();
186 } else {
187 item = new QQuickItem;
188
189 if (!info.viewBox.isEmpty()) {
190 auto xformProp = item->transform();
191 if (!qFuzzyIsNull(info.viewBox.x()) || !qFuzzyIsNull(info.viewBox.y())) {
192 auto *translate = new QQuickTranslate(item);
193 translate->setX(-info.viewBox.x());
194 translate->setY(-info.viewBox.y());
195 xformProp.append(&xformProp, translate);
196 }
197 auto *scale = new QQuickScale(item);
198 scale->setXScale(info.size.width() / info.viewBox.width());
199 scale->setYScale(info.size.height() / info.viewBox.height());
200 xformProp.append(&xformProp, scale);
201 }
202 }
203
204 pushItem(item);
205 generateNodeBase(info);
206 } else {
207 QQuickItem *item = popItem();
208 QQuickItem *effectItem = item;
209 if (!info.filterId.isEmpty())
210 effectItem = generateFilter(item, info);
211 if (!info.maskId.isEmpty())
212 generateMask(effectItem ? effectItem : item, info);
213 }
214
215 return true;
216}
217
218void QQuickItemGenerator::generatePath(const PathNodeInfo &info, const QRectF &overrideBoundingRect)
219{
220 if (Q_UNLIKELY(errorState()))
221 return;
222
223 if (auto *rec = activeRecord()) {
224 rec->append(
225 [this, info, overrideBoundingRect]() { generatePath(info, overrideBoundingRect); });
226 return;
227 }
228
229 if (!isNodeVisible(info))
230 return;
231
232 if (qobject_cast<QQuickShape *>(currentItem()) && info.markerStartId.isEmpty()
233 && info.markerMidId.isEmpty() && info.markerEndId.isEmpty() && info.filterId.isEmpty()) {
234 optimizePaths(info, overrideBoundingRect);
235 } else {
236 auto *shape = createShapeContainer();
237 pushItem(shape);
238 generateNodeBase(info);
239 optimizePaths(info, overrideBoundingRect);
240 QQuickItem *item = popItem();
241 if (!info.markerStartId.isEmpty() || !info.markerMidId.isEmpty()
242 || !info.markerEndId.isEmpty()) {
243 generateMarkers(info);
244 }
245 QQuickItem *effectItem = item;
246 if (!info.filterId.isEmpty())
247 effectItem = generateFilter(item, info);
248 if (!info.maskId.isEmpty())
249 generateMask(effectItem ? effectItem : item, info);
250 }
251}
252
253static QQuickShapeGradient *createShapeGradient(const QGradient &grad, const QRectF &coordSys,
254 QObject *parent)
255{
256 const qreal sx = coordSys.width();
257 const qreal sy = coordSys.height();
258 const qreal tx = coordSys.x();
259 const qreal ty = coordSys.y();
260
261 QQuickShapeGradient *result = nullptr;
262
263 if (grad.type() == QGradient::LinearGradient) {
264 const auto *linGrad = static_cast<const QLinearGradient *>(&grad);
265 auto *g = new QQuickShapeLinearGradient(parent);
266 g->setX1(linGrad->start().x() * sx + tx);
267 g->setY1(linGrad->start().y() * sy + ty);
268 g->setX2(linGrad->finalStop().x() * sx + tx);
269 g->setY2(linGrad->finalStop().y() * sy + ty);
270 result = g;
271 } else if (grad.type() == QGradient::RadialGradient) {
272 const auto *radGrad = static_cast<const QRadialGradient *>(&grad);
273 auto *g = new QQuickShapeRadialGradient(parent);
274 g->setCenterX(radGrad->center().x() * sx + tx);
275 g->setCenterY(radGrad->center().y() * sy + ty);
276 g->setCenterRadius(radGrad->radius() * sx);
277 g->setFocalX(radGrad->focalPoint().x() * sx + tx);
278 g->setFocalY(radGrad->focalPoint().y() * sy + ty);
279 result = g;
280 } else {
281 return nullptr;
282 }
283
284 for (const auto &stop : grad.stops()) {
285 auto *s = new QQuickGradientStop(result);
286 s->setPosition(stop.first);
287 s->setColor(stop.second);
288 auto stopsProp = result->stops();
289 stopsProp.append(&stopsProp, s);
290 }
291 result->setSpread(QQuickShapeGradient::SpreadMode(grad.spread()));
292 return result;
293}
294
295void QQuickItemGenerator::outputShapePath(const PathNodeInfo &info, const QPainterPath *path,
296 const QQuadPath *quadPath,
297 QQuickVectorImageGenerator::PathSelector pathSelector,
298 const QRectF &boundingRect)
299{
300 Q_ASSERT(path || quadPath);
301
302 if (Q_UNLIKELY(errorState()))
303 return;
304
305 auto *shape = qobject_cast<QQuickShape *>(currentItem());
306 if (!shape)
307 return;
308
309 const bool invalidGradientBounds = info.strokeGrad.coordinateMode() == QGradient::ObjectMode
310 && (qFuzzyIsNull(boundingRect.width()) || qFuzzyIsNull(boundingRect.height()));
311 const QColor strokeColor = info.strokeStyle.color.defaultValue().value<QColor>();
312 const bool noPen = (strokeColor == QColorConstants::Transparent || !strokeColor.isValid())
313 && !info.strokeStyle.color.isAnimated() && !info.strokeStyle.opacity.isAnimated()
314 && (info.strokeGrad.type() == QGradient::NoGradient || invalidGradientBounds);
315 if (pathSelector == QQuickVectorImageGenerator::StrokePath && noPen)
316 return;
317
318 const QColor fillColor = info.fillColor.defaultValue().value<QColor>();
319 const bool noFill = info.grad.type() == QGradient::NoGradient && info.patternId.isEmpty()
320 && fillColor == QColorConstants::Transparent && !info.fillColor.isAnimated()
321 && !info.fillOpacity.isAnimated();
322 if (pathSelector == QQuickVectorImageGenerator::FillPath && noFill)
323 return;
324
325 if (noPen && noFill)
326 return;
327
328 auto *shapePath = new QQuickShapePath;
329 shapePath->setParent(shape);
330
331 if (!info.nodeId.isEmpty()) {
332 switch (pathSelector) {
333 case QQuickVectorImageGenerator::FillPath:
334 shapePath->setObjectName(u"svg_fill_path:"_s + info.nodeId);
335 break;
336 case QQuickVectorImageGenerator::StrokePath:
337 shapePath->setObjectName(u"svg_stroke_path:"_s + info.nodeId);
338 break;
339 case QQuickVectorImageGenerator::FillAndStroke:
340 shapePath->setObjectName(u"svg_path:"_s + info.nodeId);
341 break;
342 }
343 }
344
345 if (noPen || !(pathSelector & QQuickVectorImageGenerator::StrokePath)) {
346 shapePath->setStrokeColor(QColorConstants::Transparent);
347 } else {
348 if (info.strokeGrad.type() != QGradient::NoGradient && !invalidGradientBounds) {
349 QRectF coordinateSys = info.strokeGrad.coordinateMode() == QGradient::ObjectMode
350 ? boundingRect
351 : QRectF(0.0, 0.0, 1.0, 1.0);
352 shapePath->setStrokeGradient(
353 createShapeGradient(info.strokeGrad, coordinateSys, shapePath));
354 } else {
355 shapePath->setStrokeColor(strokeColor);
356 }
357 shapePath->setStrokeWidth(info.strokeStyle.width.defaultValue().toReal());
358 shapePath->setCapStyle(QQuickShapePath::CapStyle(info.strokeStyle.lineCapStyle));
359 shapePath->setJoinStyle(QQuickShapePath::JoinStyle(info.strokeStyle.lineJoinStyle));
360 shapePath->setMiterLimit(info.strokeStyle.miterLimit);
361 }
362
363 QTransform fillTransform = info.fillTransform;
364 if (!(pathSelector & QQuickVectorImageGenerator::FillPath)) {
365 shapePath->setFillColor(QColorConstants::Transparent);
366 } else if (!info.patternId.isEmpty()) {
367 generatePattern(shapePath, info, boundingRect, fillTransform);
368 } else if (info.grad.type() != QGradient::NoGradient) {
369 if (info.grad.coordinateMode() == QGradient::ObjectMode) {
370 QTransform objectToUserSpace;
371 objectToUserSpace.translate(boundingRect.x(), boundingRect.y());
372 objectToUserSpace.scale(boundingRect.width(), boundingRect.height());
373 fillTransform *= objectToUserSpace;
374 }
375 shapePath->setFillGradient(
376 createShapeGradient(info.grad, QRectF(0.0, 0.0, 1.0, 1.0), shapePath));
377 } else {
378 shapePath->setFillColor(fillColor);
379 }
380 if (!fillTransform.isIdentity())
381 shapePath->setFillTransform(QMatrix4x4(fillTransform));
382
383 shapePath->setFillRule(
384 QQuickShapePath::FillRule(path ? path->fillRule() : quadPath->fillRule()));
385
386 if (quadPath)
387 shapePath->setPathHints(QQuickShapePath::PathHints(int(quadPath->pathHints())));
388
389 const QString svgString = path ? QQuickVectorImageGenerator::Utils::toSvgString(*path)
390 : QQuickVectorImageGenerator::Utils::toSvgString(*quadPath);
391 auto *pathSvg = new QQuickPathSvg(shapePath);
392 pathSvg->setPath(svgString);
393 auto pathElems = shapePath->pathElements();
394 pathElems.append(&pathElems, pathSvg);
395
396 auto shapeData = shape->data();
397 shapeData.append(&shapeData, shapePath);
398}
399
400void QQuickItemGenerator::generateImageNode(const ImageNodeInfo &info)
401{
402 if (Q_UNLIKELY(errorState()))
403 return;
404
405 if (auto *rec = activeRecord()) {
406 rec->append([this, info]() { generateImageNode(info); });
407 return;
408 }
409
410 if (!isNodeVisible(info))
411 return;
412
413 QString filePath = info.externalFileReference;
414 if (filePath.isEmpty()) {
415 filePath =
416 QDir::tempPath() + QStringLiteral("/svg_asset_%1.png").arg(info.image.cacheKey());
417 if (!info.image.save(filePath))
418 qCWarning(lcQuickVectorImage) << "Unable to save image resource" << filePath;
419 } else if (QDir::isRelativePath(filePath)) {
420 filePath = QFileInfo(fileName()).dir().absoluteFilePath(filePath);
421 }
422
423 auto *image = new QQuickImage;
424 if (m_context)
425 QQmlEngine::setContextForObject(image, m_context);
426 pushItem(image);
427 generateNodeBase(info);
428 image->setX(info.rect.x());
429 image->setY(info.rect.y());
430 image->setWidth(info.rect.width());
431 image->setHeight(info.rect.height());
432 auto *parserStatus = qobject_cast<QQmlParserStatus *>(image);
433 parserStatus->classBegin();
434 image->setSource(QUrl::fromLocalFile(filePath));
435 parserStatus->componentComplete();
436 QQuickItem *item = popItem();
437 {
438 QQuickItem *effectItem = item;
439 if (!info.filterId.isEmpty())
440 effectItem = generateFilter(item, info);
441 if (!info.maskId.isEmpty())
442 generateMask(effectItem ? effectItem : item, info);
443 }
444}
445
446void QQuickItemGenerator::generateTextNode(const TextNodeInfo &info)
447{
448 if (Q_UNLIKELY(errorState()))
449 return;
450
451 if (auto *rec = activeRecord()) {
452 rec->append([this, info]() { generateTextNode(info); });
453 return;
454 }
455
456 if (!isNodeVisible(info))
457 return;
458
459 auto *item = new QQuickItem;
460 pushItem(item);
461 generateNodeBase(info);
462
463 auto *text = new QQuickText;
464 text->setParent(item);
465 text->setParentItem(item);
466
467 text->setColor(info.fillColor.defaultValue().value<QColor>());
468 text->setFont(info.font);
469 text->setText(info.text);
470 text->setTextFormat(info.needsRichText ? QQuickText::RichText : QQuickText::StyledText);
471
472 if (info.isTextArea) {
473 text->setX(info.position.x());
474 text->setY(info.position.y());
475 if (info.size.width() > 0)
476 text->setWidth(info.size.width());
477 if (info.size.height() > 0)
478 text->setHeight(info.size.height());
479 text->setWrapMode(QQuickText::Wrap);
480 text->setClip(true);
481 } else {
482 QFontMetricsF fm(info.font);
483 text->setX(info.position.x());
484 text->setY(info.position.y() - fm.ascent());
485 switch (info.alignment) {
486 case Qt::AlignHCenter:
487 text->setHAlign(QQuickText::AlignHCenter);
488 break;
489 case Qt::AlignRight:
490 text->setHAlign(QQuickText::AlignRight);
491 break;
492 default:
493 text->setHAlign(QQuickText::AlignLeft);
494 break;
495 }
496 }
497
498 const QColor strokeColor = info.strokeColor.defaultValue().value<QColor>();
499 if (strokeColor != QColorConstants::Transparent || info.strokeColor.isAnimated()) {
500 text->setStyleColor(strokeColor);
501 text->setStyle(QQuickText::Outline);
502 }
503
504 QQuickItem *textItem = popItem();
505 {
506 QQuickItem *effectItem = textItem;
507 if (!info.filterId.isEmpty())
508 effectItem = generateFilter(textItem, info);
509 if (!info.maskId.isEmpty())
510 generateMask(effectItem ? effectItem : textItem, info);
511 }
512}
513
514void QQuickItemGenerator::generateNode(const NodeInfo &info)
515{
516 if (auto *rec = activeRecord()) {
517 rec->append([this, info]() { generateNode(info); });
518 return;
519 }
520 qCDebug(lcQuickVectorImage) << "generateNode: not yet implemented";
521 Q_UNUSED(info)
522}
523
524void QQuickItemGenerator::generateUseNode(const UseNodeInfo &info)
525{
526 if (Q_UNLIKELY(errorState()))
527 return;
528
529 if (auto *rec = activeRecord()) {
530 rec->append([this, info]() { generateUseNode(info); });
531 return;
532 }
533
534 if (!isNodeVisible(info))
535 return;
536
537 if (info.stage == StructureNodeStage::Start) {
538 auto *item = new QQuickItem;
539 pushItem(item);
540 generateNodeBase(info);
541 } else {
542 QQuickItem *item = popItem();
543 QQuickItem *effectItem = item;
544 if (!info.filterId.isEmpty())
545 effectItem = generateFilter(item, info);
546 if (!info.maskId.isEmpty())
547 generateMask(effectItem ? effectItem : item, info);
548 }
549}
550
551bool QQuickItemGenerator::generateDefsNode(const StructureNodeInfo &info)
552{
553 if (Q_UNLIKELY(errorState()))
554 return false;
555
556 if (info.stage == StructureNodeStage::Start) {
557 m_defs[info.id] = {};
558 m_currentDefsRecord = &m_defs[info.id];
559 } else {
560 m_currentDefsRecord = nullptr;
561 auto it = m_defs.find(info.id);
562 if (it != m_defs.end()) {
563 auto *container = new QQuickItem;
564 m_itemStack.push(container);
565 for (const auto &step : *it)
566 step();
567 m_itemStack.pop();
568 }
569 }
570 return true;
571}
572
573void QQuickItemGenerator::generateDefsInstantiationNode(const StructureNodeInfo &info)
574{
575 if (Q_UNLIKELY(errorState()))
576 return;
577
578 if (auto *rec = activeRecord()) {
579 rec->append([this, info]() { generateDefsInstantiationNode(info); });
580 return;
581 }
582
583 if (info.stage != StructureNodeStage::Start)
584 return;
585
586 auto it = m_defs.find(info.defsId);
587 if (it == m_defs.end()) {
588 qCWarning(lcQuickVectorImage)
589 << "generateDefsInstantiationNode: unknown defs id:" << info.defsId;
590 return;
591 }
592 for (const auto &step : *it)
593 step();
594}
595
596bool QQuickItemGenerator::generateMaskNode(const MaskNodeInfo &info)
597{
598 if (Q_UNLIKELY(errorState()))
599 return false;
600
601 if (auto *rec = activeRecord()) {
602 rec->append([this, info]() { generateMaskNode(info); });
603 return true;
604 }
605
606 if (info.stage == StructureNodeStage::Start) {
607 if (info.isMaskContentRelativeCoordinates) {
608 auto *transformerItem = new QQuickItem;
609 pushItem(transformerItem);
610 }
611 return true;
612 }
613
614 generateMaskContainer(info);
615 return true;
616}
617
618static QQuickShaderEffectSource *makeSES(QQuickItem *item, const QRectF &rect, QQuickItem *parent)
619{
620 auto *ses = new QQuickShaderEffectSource;
621 ses->setSourceItem(item);
622 ses->setWidth(rect.width());
623 ses->setHeight(rect.height());
624 ses->setVisible(false);
625 ses->setParent(parent);
626 ses->setParentItem(parent);
627 return ses;
628}
629
630static QQuickShaderEffect *makeFilterEffect(QQuickItem *inputItem, const QUrl &shader,
631 QQmlContext *context, QQuickItem *parent)
632{
633 auto *effect = new QQuickShaderEffect;
634 if (context)
635 QQmlEngine::setContextForObject(effect, context);
636 auto *parserStatus = qobject_cast<QQmlParserStatus *>(effect);
637 parserStatus->classBegin();
638 effect->bindableWidth().setBinding([inputItem] { return inputItem->width(); });
639 effect->bindableHeight().setBinding([inputItem] { return inputItem->height(); });
640 effect->setVisible(false);
641 effect->setParent(parent);
642 effect->setParentItem(parent);
643 effect->setFragmentShader(shader);
644 return effect;
645}
646
647static QQuickShaderEffectSource *makeEffectSES(QQuickShaderEffect *effect, const QRectF &stepRect,
648 const QRectF &filterRect, QQuickItem *parent)
649{
650 auto *wrapper = new QQuickItem;
651 wrapper->setWidth(stepRect.width());
652 wrapper->setHeight(stepRect.height());
653 wrapper->setClip(true);
654 wrapper->setParent(parent);
655 wrapper->setParentItem(parent);
656 effect->setVisible(true);
657 effect->setParentItem(wrapper);
658 effect->setX(filterRect.x() - stepRect.x());
659 effect->setY(filterRect.y() - stepRect.y());
660 auto *ses = makeSES(wrapper, stepRect, parent);
661 ses->setHideSource(true);
662 return ses;
663}
664
665void QQuickItemGenerator::bindTextureSize(QQuickShaderEffectSource *ses)
666{
667 if (!m_topLevelScaleSpy)
668 return;
669
670 QQuickItemSpy *scaleSpy = m_topLevelScaleSpy;
671 auto updateTextureSize = [ses, scaleSpy]() {
672 const QSizeF unitScale = scaleSpy->requiredTextureSize();
673 const qreal width = ses->width() * unitScale.width();
674 const qreal height = ses->height() * unitScale.height();
675 ses->setTextureSize(QSize(qRound(width), qRound(height)));
676 };
677 QObject::connect(scaleSpy, &QQuickItemSpy::requiredTextureSizeChanged, ses, updateTextureSize);
678 QObject::connect(ses, &QQuickItem::widthChanged, ses, updateTextureSize);
679 QObject::connect(ses, &QQuickItem::heightChanged, ses, updateTextureSize);
680 updateTextureSize();
681}
682
683void QQuickItemGenerator::generateMaskContainer(const MaskNodeInfo &info)
684{
685 QQuickItem *transformer = nullptr;
686 QQuickMatrix4x4 *transformerMatrix = nullptr;
687 if (info.isMaskContentRelativeCoordinates) {
688 transformer = popItem();
689 transformerMatrix = new QQuickMatrix4x4(transformer);
690 auto xformProp = transformer->transform();
691 xformProp.append(&xformProp, transformerMatrix);
692 }
693
694 auto *container = currentItem();
695 m_maskDefs[info.id] = { container,
696 info.maskRect,
697 info.isMaskRectRelativeCoordinates,
698 info.isMaskContentRelativeCoordinates,
699 transformer,
700 transformerMatrix };
701}
702
703void QQuickItemGenerator::generateMask(QQuickItem *item, const NodeInfo &info)
704{
705 auto it = m_maskDefs.find(info.maskId);
706 if (it == m_maskDefs.end()) {
707 qCWarning(lcQuickVectorImage) << "generateMask: unknown mask id:" << info.maskId;
708 return;
709 }
710 MaskDef &maskDef = *it;
711 QQuickItem *parentItem = item->parentItem();
712
713 const qreal w = item->width();
714 const qreal h = item->height();
715
716 const QRectF svgBounds =
717 info.bounds.isNull() ? QRectF(item->x(), item->y(), w, h) : info.bounds;
718 QRectF svgMaskRect;
719 if (maskDef.isMaskRectRelativeCoordinates) {
720 svgMaskRect = QRectF(maskDef.maskRect.x() * svgBounds.width() + svgBounds.x(),
721 maskDef.maskRect.y() * svgBounds.height() + svgBounds.y(),
722 maskDef.maskRect.width() * svgBounds.width(),
723 maskDef.maskRect.height() * svgBounds.height());
724 } else {
725 svgMaskRect = maskDef.maskRect;
726 }
727
728 if (maskDef.isMaskContentRelativeCoordinates && maskDef.transformerMatrix) {
729 QMatrix4x4 mat;
730 mat.translate(svgBounds.x(), svgBounds.y());
731 mat.scale(svgBounds.width(), svgBounds.height(), 1.0f);
732 maskDef.transformerMatrix->setMatrix(mat);
733 }
734
735 maskDef.container->setParent(m_rootItem);
736 maskDef.container->setParentItem(m_rootItem);
737 const qreal containerW = m_containerSize.width() > 0 ? m_containerSize.width() : w;
738 const qreal containerH = m_containerSize.height() > 0 ? m_containerSize.height() : h;
739 maskDef.container->setWidth(containerW);
740 maskDef.container->setHeight(containerH);
741
742 static const QUrl maskShaderUrl(
743 u"qrc:/qt-project.org/quickvectorimage/helpers/shaders_ng/genericmask.frag.qsb"_s);
744
745 auto *maskSES = makeSES(maskDef.container, svgMaskRect, m_rootItem);
746 maskSES->setHideSource(true);
747 maskSES->setSourceRect(svgMaskRect);
748 bindTextureSize(maskSES);
749
750 auto *itemSES = makeSES(item, svgMaskRect, m_rootItem);
751 itemSES->setHideSource(true);
752 if (qobject_cast<QQuickShaderEffectSource *>(item))
753 itemSES->setSourceRect(QRectF(0, 0, item->width(), item->height()));
754 else
755 itemSES->setSourceRect(svgMaskRect);
756 bindTextureSize(itemSES);
757
758 auto *shaderEffect = new QQuickShaderEffect;
759 if (m_context)
760 QQmlEngine::setContextForObject(shaderEffect, m_context);
761 auto *parserStatus = qobject_cast<QQmlParserStatus *>(shaderEffect);
762 parserStatus->classBegin();
763 shaderEffect->setFragmentShader(maskShaderUrl);
764 shaderEffect->setProperty("source", QVariant::fromValue<QQuickItem *>(itemSES));
765 shaderEffect->setProperty("maskSource", QVariant::fromValue<QQuickItem *>(maskSES));
766 shaderEffect->setProperty("isAlpha", info.isMaskAlpha);
767 shaderEffect->setProperty("isInverted", info.isMaskInverted);
768 parserStatus->componentComplete();
769
770 if (!info.isDefaultOpacity)
771 shaderEffect->setOpacity(info.opacity.defaultValue().toReal());
772
773 shaderEffect->setTransformOrigin(QQuickItem::TopLeft);
774 shaderEffect->setParent(parentItem);
775 shaderEffect->setParentItem(parentItem);
776 shaderEffect->setWidth(svgMaskRect.width());
777 shaderEffect->setHeight(svgMaskRect.height());
778
779 if (!info.isDefaultTransform) {
780 const QTransform elementXf = info.transform.defaultValue().value<QTransform>();
781 QMatrix4x4 mat(elementXf);
782 mat.translate(svgMaskRect.x(), svgMaskRect.y());
783 auto *matrix = new QQuickMatrix4x4(shaderEffect);
784 matrix->setMatrix(mat);
785 auto xformProp = shaderEffect->transform();
786 xformProp.append(&xformProp, matrix);
787 shaderEffect->setX(0);
788 shaderEffect->setY(0);
789 } else {
790 shaderEffect->setX(svgMaskRect.x());
791 shaderEffect->setY(svgMaskRect.y());
792 }
793}
794
795void QQuickItemGenerator::generateFilterNode(const FilterNodeInfo &info)
796{
797 if (auto *rec = activeRecord()) {
798 rec->append([this, info]() { generateFilterNode(info); });
799 return;
800 }
801 m_filterDefs[info.id] = info;
802}
803
804static QRectF resolveRect(const QRectF &rect, FilterNodeInfo::CoordinateSystem cs,
805 const QRectF &itemBounds)
806{
808 return QRectF(itemBounds.x() + rect.x() * itemBounds.width(),
809 itemBounds.y() + rect.y() * itemBounds.height(),
810 rect.width() * itemBounds.width(), rect.height() * itemBounds.height());
811 return rect;
812}
813
814QQuickItem *QQuickItemGenerator::generateFilter(QQuickItem *item, const NodeInfo &info)
815{
816 auto it = m_filterDefs.find(info.filterId);
817 if (it == m_filterDefs.end()) {
818 qCWarning(lcQuickVectorImage) << "applyFilter: unknown filter id:" << info.filterId;
819 return nullptr;
820 }
821 const FilterNodeInfo &filterInfo = *it;
822
823 if (filterInfo.steps.isEmpty())
824 return nullptr;
825
826 QQuickItem *parentItem = item->parentItem();
827 const QRectF itemBounds = info.bounds.isNull()
828 ? QRectF(item->x(), item->y(), item->width(), item->height())
829 : info.bounds;
830
831 QRectF filterRect = resolveRect(filterInfo.filterRect, filterInfo.csFilterRect, itemBounds);
832 if (filterRect.isEmpty())
833 filterRect = itemBounds;
834
835 auto *sourceGraphic = makeSES(item, filterRect, m_rootItem);
836 sourceGraphic->setHideSource(true);
837 sourceGraphic->setSourceRect(filterRect);
838 if (filterInfo.wrapMode == QSGTexture::Repeat)
839 sourceGraphic->setWrapMode(QQuickShaderEffectSource::Repeat);
840
841 if (filterInfo.csFilterRect == FilterNodeInfo::CoordinateSystem::Relative) {
842 const qreal wFactor = filterInfo.filterRect.width();
843 const qreal hFactor = filterInfo.filterRect.height();
844 sourceGraphic->bindableWidth().setBinding(
845 [item, wFactor] { return item->width() * wFactor; });
846 sourceGraphic->bindableHeight().setBinding(
847 [item, hFactor] { return item->height() * hFactor; });
848
849 const QRectF fractions = filterInfo.filterRect;
850 auto updateSourceRect = [sourceGraphic, item, fractions]() {
851 sourceGraphic->setSourceRect(QRectF(item->x() + fractions.x() * item->width(),
852 item->y() + fractions.y() * item->height(),
853 fractions.width() * item->width(),
854 fractions.height() * item->height()));
855 };
856 QObject::connect(item, &QQuickItem::widthChanged, sourceGraphic, updateSourceRect);
857 QObject::connect(item, &QQuickItem::heightChanged, sourceGraphic, updateSourceRect);
858 }
859
860 bindTextureSize(sourceGraphic);
861
862 QHash<QString, QQuickShaderEffectSource *> namedOutputs;
863 const QString sourceAlphaName = filterInfo.id + u"_source_alpha"_s;
864 QQuickShaderEffectSource *sourceAlpha = nullptr;
865 QQuickShaderEffectSource *lastOutput = sourceGraphic;
866
867 const auto resolveInput = [&](FilterNodeInfo::FilterInput inputType,
868 const QString &name) -> QQuickShaderEffectSource * {
869 switch (inputType) {
870 case FilterNodeInfo::FilterInput::SourceColor:
871 return sourceGraphic;
872 case FilterNodeInfo::FilterInput::SourceAlpha:
873 return sourceAlpha;
874 case FilterNodeInfo::FilterInput::Name:
875 return namedOutputs.value(name, sourceGraphic);
876 default:
877 return lastOutput;
878 }
879 };
880
881 QRectF lastStepRect = filterRect;
882
883 for (int i = 0; i < filterInfo.steps.size(); ++i) {
884 const FilterNodeInfo::FilterStep &step = filterInfo.steps.at(i);
885 const QRectF stepRect =
886 (step.filterPrimitiveRect.isNull()
887 || step.csFilterParameter == FilterNodeInfo::CoordinateSystem::MatchFilterRect)
888 ? filterRect
889 : resolveRect(step.filterPrimitiveRect, step.csFilterParameter, itemBounds);
890 QQuickShaderEffectSource *output = nullptr;
891 if (step.filterType == FilterNodeInfo::Type::Merge) {
892 QList<QQuickShaderEffectSource *> mergeInputs;
893 while (i + 1 < filterInfo.steps.size()
894 && filterInfo.steps.at(i + 1).filterType == FilterNodeInfo::Type::MergeNode) {
895 ++i;
896 const FilterNodeInfo::FilterStep &mergeNode = filterInfo.steps.at(i);
897 mergeInputs.append(resolveInput(mergeNode.input1, mergeNode.namedInput1));
898 }
899 output = generateFilterMerge(mergeInputs, stepRect, filterRect);
900 } else {
901 auto *input1 = resolveInput(step.input1, step.namedInput1);
902 auto *input2 = resolveInput(step.input2, step.namedInput2);
903 output = generateFilterStep(step, input1, input2, stepRect, filterRect);
904 }
905
906 if (output) {
907 bindTextureSize(output);
908 lastOutput = output;
909 lastStepRect = stepRect;
910 if (!step.outputName.isEmpty()) {
911 namedOutputs[step.outputName] = output;
912 if (step.outputName == sourceAlphaName)
913 sourceAlpha = output;
914 }
915 }
916 }
917
918 if (lastOutput == sourceGraphic)
919 return nullptr;
920
921 lastOutput->setParent(parentItem);
922 lastOutput->setParentItem(parentItem);
923 lastOutput->setVisible(true);
924
925 if (!info.isDefaultOpacity)
926 lastOutput->setOpacity(info.opacity.defaultValue().toReal());
927
928 if (!info.isDefaultTransform) {
929 const QTransform elementXf = info.transform.defaultValue().value<QTransform>();
930 QMatrix4x4 mat(elementXf);
931 mat.translate(lastStepRect.x(), lastStepRect.y());
932 auto *matrix = new QQuickMatrix4x4(lastOutput);
933 matrix->setMatrix(mat);
934 auto xformProp = lastOutput->transform();
935 xformProp.append(&xformProp, matrix);
936 } else {
937 lastOutput->setX(lastStepRect.x());
938 lastOutput->setY(lastStepRect.y());
939 }
940
941 return lastOutput;
942}
943
944QQuickShaderEffectSource *QQuickItemGenerator::generateFilterStep(
945 const FilterNodeInfo::FilterStep &step, QQuickShaderEffectSource *input1,
946 QQuickShaderEffectSource *input2, const QRectF &stepRect, const QRectF &filterRect)
947{
948 switch (step.filterType) {
949 case FilterNodeInfo::Type::Flood:
950 return generateFilterFlood(step, input1, stepRect, filterRect);
951 case FilterNodeInfo::Type::Offset:
952 return generateFilterOffset(step, input1, stepRect);
953 case FilterNodeInfo::Type::ColorMatrix:
954 return generateFilterColorMatrix(step, input1, stepRect, filterRect);
955 case FilterNodeInfo::Type::BlendNormal:
956 case FilterNodeInfo::Type::BlendMultiply:
957 case FilterNodeInfo::Type::BlendScreen:
958 case FilterNodeInfo::Type::BlendDarken:
959 case FilterNodeInfo::Type::BlendLighten:
960 return generateFilterBlend(step, input1, input2, stepRect, filterRect);
961 case FilterNodeInfo::Type::CompositeOver:
962 case FilterNodeInfo::Type::CompositeIn:
963 case FilterNodeInfo::Type::CompositeOut:
964 case FilterNodeInfo::Type::CompositeAtop:
965 case FilterNodeInfo::Type::CompositeXor:
966 case FilterNodeInfo::Type::CompositeLighter:
967 case FilterNodeInfo::Type::CompositeArithmetic:
968 return generateFilterComposite(step, input1, input2, stepRect, filterRect);
969 case FilterNodeInfo::Type::GaussianBlur:
970 return generateFilterGaussianBlur(step, input1, stepRect, filterRect);
971 default:
972 qCDebug(lcQuickVectorImage) << "generateFilterStep: filter type not yet implemented";
973 return nullptr;
974 }
975}
976
977QQuickShaderEffectSource *
978QQuickItemGenerator::generateFilterMerge(const QList<QQuickShaderEffectSource *> &inputs,
979 const QRectF &stepRect, const QRectF &filterRect)
980{
981 const int maxNodeCount = 8;
982 if (inputs.isEmpty()) {
983 qCWarning(lcQuickVectorImage) << "generateFilterMerge: requires at least one input";
984 return nullptr;
985 }
986 if (inputs.size() > maxNodeCount)
987 qCWarning(lcQuickVectorImage)
988 << "generateFilterMerge: maximum of" << maxNodeCount << "nodes exceeded";
989
990 static const QUrl shader(
991 u"qrc:/qt-project.org/quickvectorimage/helpers/shaders_ng/femerge.frag.qsb"_s);
992 auto *effect = makeFilterEffect(inputs.first(), shader, m_context, m_rootItem);
993
994 const int count = qMin(maxNodeCount, inputs.size());
995 effect->setProperty("sourceCount", count);
996 for (int i = 0; i < maxNodeCount; ++i) {
997 QQuickItem *src = i < inputs.size() ? inputs.at(i) : nullptr;
998 effect->setProperty(QStringLiteral("source%1").arg(i + 1).toLatin1(),
999 QVariant::fromValue(src));
1000 }
1001
1002 qobject_cast<QQmlParserStatus *>(effect)->componentComplete();
1003 return makeEffectSES(effect, stepRect, filterRect, m_rootItem);
1004}
1005
1006QQuickShaderEffectSource *
1007QQuickItemGenerator::generateFilterFlood(const FilterNodeInfo::FilterStep &step,
1008 QQuickShaderEffectSource *input, const QRectF &stepRect,
1009 const QRectF &filterRect)
1010{
1011 auto *rect = new QQuickRectangle;
1012 rect->setColor(step.filterParameter.value<QColor>());
1013 rect->setVisible(false);
1014 rect->setParent(m_rootItem);
1015 rect->setParentItem(m_rootItem);
1016 if (input) {
1017 rect->bindableWidth().setBinding([input] { return input->width(); });
1018 rect->bindableHeight().setBinding([input] { return input->height(); });
1019 } else {
1020 rect->setWidth(filterRect.width());
1021 rect->setHeight(filterRect.height());
1022 }
1023 auto *ses = makeSES(rect, stepRect, m_rootItem);
1024 ses->setSourceRect(QRectF(stepRect.x() - filterRect.x(), stepRect.y() - filterRect.y(),
1025 stepRect.width(), stepRect.height()));
1026 return ses;
1027}
1028
1029QQuickShaderEffectSource *
1030QQuickItemGenerator::generateFilterOffset(const FilterNodeInfo::FilterStep &step,
1031 QQuickShaderEffectSource *input, const QRectF &stepRect)
1032{
1033 const QVector2D offset = step.filterParameter.value<QVector2D>();
1034 const qreal offsetX = step.csFilterParameter == FilterNodeInfo::CoordinateSystem::Relative
1035 ? offset.x() * stepRect.width()
1036 : offset.x();
1037 const qreal offsetY = step.csFilterParameter == FilterNodeInfo::CoordinateSystem::Relative
1038 ? offset.y() * stepRect.height()
1039 : offset.y();
1040
1041 auto *ses = makeSES(input, stepRect, m_rootItem);
1042 ses->setSourceRect(QRectF(-offsetX, -offsetY, stepRect.width(), stepRect.height()));
1043 return ses;
1044}
1045
1046QQuickShaderEffectSource *
1047QQuickItemGenerator::generateFilterColorMatrix(const FilterNodeInfo::FilterStep &step,
1048 QQuickShaderEffectSource *input,
1049 const QRectF &stepRect, const QRectF &filterRect)
1050{
1051 static const QUrl shader(
1052 u"qrc:/qt-project.org/quickvectorimage/helpers/shaders_ng/fecolormatrix.frag.qsb"_s);
1053 auto *effect = makeFilterEffect(input, shader, m_context, m_rootItem);
1054 effect->setProperty("source", QVariant::fromValue(input));
1055
1056 const auto matrix = step.filterParameter.value<QGenericMatrix<5, 5, qreal>>();
1057 for (int row = 0; row < 4; ++row) {
1058 for (int col = 0; col < 5; ++col) {
1059 effect->setProperty(QStringLiteral("m_%1_%2").arg(row).arg(col).toLatin1(),
1060 matrix(col, row));
1061 }
1062 }
1063
1064 qobject_cast<QQmlParserStatus *>(effect)->componentComplete();
1065 return makeEffectSES(effect, stepRect, filterRect, m_rootItem);
1066}
1067
1068QQuickShaderEffectSource *QQuickItemGenerator::generateFilterBlend(
1069 const FilterNodeInfo::FilterStep &step, QQuickShaderEffectSource *input1,
1070 QQuickShaderEffectSource *input2, const QRectF &stepRect, const QRectF &filterRect)
1071{
1072 QString shaderName;
1073 switch (step.filterType) {
1074 case FilterNodeInfo::Type::BlendNormal:
1075 shaderName = u"feblendnormal"_s;
1076 break;
1077 case FilterNodeInfo::Type::BlendMultiply:
1078 shaderName = u"feblendmultiply"_s;
1079 break;
1080 case FilterNodeInfo::Type::BlendScreen:
1081 shaderName = u"feblendscreen"_s;
1082 break;
1083 case FilterNodeInfo::Type::BlendDarken:
1084 shaderName = u"feblenddarken"_s;
1085 break;
1086 case FilterNodeInfo::Type::BlendLighten:
1087 shaderName = u"feblendlighten"_s;
1088 break;
1089 default:
1090 Q_UNREACHABLE();
1091 }
1092
1093 const QUrl shaderUrl(u"qrc:/qt-project.org/quickvectorimage/helpers/shaders_ng/"_s + shaderName
1094 + u".frag.qsb"_s);
1095 auto *effect = makeFilterEffect(input1, shaderUrl, m_context, m_rootItem);
1096 effect->setProperty("source", QVariant::fromValue(input1));
1097 effect->setProperty("source2", QVariant::fromValue(input2));
1098 qobject_cast<QQmlParserStatus *>(effect)->componentComplete();
1099 return makeEffectSES(effect, stepRect, filterRect, m_rootItem);
1100}
1101
1102QQuickShaderEffectSource *QQuickItemGenerator::generateFilterComposite(
1103 const FilterNodeInfo::FilterStep &step, QQuickShaderEffectSource *input1,
1104 QQuickShaderEffectSource *input2, const QRectF &stepRect, const QRectF &filterRect)
1105{
1106 QString shaderName;
1107 switch (step.filterType) {
1108 case FilterNodeInfo::Type::CompositeOver:
1109 shaderName = u"fecompositeover"_s;
1110 break;
1111 case FilterNodeInfo::Type::CompositeIn:
1112 shaderName = u"fecompositein"_s;
1113 break;
1114 case FilterNodeInfo::Type::CompositeOut:
1115 shaderName = u"fecompositeout"_s;
1116 break;
1117 case FilterNodeInfo::Type::CompositeAtop:
1118 shaderName = u"fecompositeatop"_s;
1119 break;
1120 case FilterNodeInfo::Type::CompositeXor:
1121 shaderName = u"fecompositexor"_s;
1122 break;
1123 case FilterNodeInfo::Type::CompositeLighter:
1124 shaderName = u"fecompositelighter"_s;
1125 break;
1126 case FilterNodeInfo::Type::CompositeArithmetic:
1127 shaderName = u"fecompositearithmetic"_s;
1128 break;
1129 default:
1130 Q_UNREACHABLE();
1131 }
1132
1133 const QUrl shaderUrl(u"qrc:/qt-project.org/quickvectorimage/helpers/shaders_ng/"_s + shaderName
1134 + u".frag.qsb"_s);
1135 auto *effect = makeFilterEffect(input1, shaderUrl, m_context, m_rootItem);
1136 effect->setProperty("source", QVariant::fromValue(input1));
1137 effect->setProperty("source2", QVariant::fromValue(input2));
1138 if (step.filterType == FilterNodeInfo::Type::CompositeArithmetic)
1139 effect->setProperty("k", QVariant::fromValue(step.filterParameter.value<QVector4D>()));
1140 qobject_cast<QQmlParserStatus *>(effect)->componentComplete();
1141 return makeEffectSES(effect, stepRect, filterRect, m_rootItem);
1142}
1143
1144QQuickShaderEffectSource *
1145QQuickItemGenerator::generateFilterGaussianBlur(const FilterNodeInfo::FilterStep &step,
1146 QQuickShaderEffectSource *input,
1147 const QRectF &stepRect, const QRectF &filterRect)
1148{
1149 constexpr qreal maxDeviation = 12.0;
1150 const qreal deviation = step.filterParameter.toReal();
1151 const qreal blurValue = step.csFilterParameter == FilterNodeInfo::CoordinateSystem::Relative
1152 ? std::min(1.0, deviation * stepRect.width() / maxDeviation)
1153 : std::min(1.0, deviation / maxDeviation);
1154
1155 auto *effect = new QQuickMultiEffect;
1156 if (m_context)
1157 QQmlEngine::setContextForObject(effect, m_context);
1158 auto *parserStatus = qobject_cast<QQmlParserStatus *>(effect);
1159 parserStatus->classBegin();
1160 effect->bindableWidth().setBinding([input] { return input->width(); });
1161 effect->bindableHeight().setBinding([input] { return input->height(); });
1162 effect->setVisible(false);
1163 effect->setParent(m_rootItem);
1164 effect->setParentItem(m_rootItem);
1165 effect->setSource(input);
1166 effect->setBlurEnabled(true);
1167 effect->setBlur(blurValue);
1168 effect->setBlurMax(64);
1169 parserStatus->componentComplete();
1170
1171 auto *wrapper = new QQuickItem;
1172 wrapper->setWidth(stepRect.width());
1173 wrapper->setHeight(stepRect.height());
1174 wrapper->setClip(true);
1175 wrapper->setParent(m_rootItem);
1176 wrapper->setParentItem(m_rootItem);
1177 effect->setVisible(true);
1178 effect->setParentItem(wrapper);
1179 effect->setX(filterRect.x() - stepRect.x());
1180 effect->setY(filterRect.y() - stepRect.y());
1181 auto *ses = makeSES(wrapper, stepRect, m_rootItem);
1182 ses->setHideSource(true);
1183 return ses;
1184}
1185
1186bool QQuickItemGenerator::generateMarkerNode(const MarkerNodeInfo &info)
1187{
1188 if (Q_UNLIKELY(errorState()))
1189 return false;
1190
1191 if (m_currentDefsRecord) {
1192 m_currentDefsRecord->append([this, info]() { generateMarkerNode(info); });
1193 return true;
1194 }
1195
1196 if (info.stage == StructureNodeStage::Start) {
1197 m_markerDefs[info.id].info = info;
1198 m_currentMarkerRecord = &m_markerDefs[info.id].recording;
1199 return true;
1200 }
1201
1202 m_markerDefs[info.id].info = info;
1203 m_currentMarkerRecord = nullptr;
1204 return true;
1205}
1206
1207bool QQuickItemGenerator::generatePatternNode(const PatternNodeInfo &info)
1208{
1209 if (Q_UNLIKELY(errorState()))
1210 return false;
1211
1212 if (auto *rec = activeRecord()) {
1213 rec->append([this, info]() { generatePatternNode(info); });
1214 return true;
1215 }
1216
1217 if (info.stage == StructureNodeStage::Start) {
1218 auto *containerItem = new QQuickItem;
1219 pushItem(containerItem);
1220 return true;
1221 }
1222
1223 generatePatternContainer(info);
1224 return true;
1225}
1226
1227void QQuickItemGenerator::generatePatternContainer(const PatternNodeInfo &info)
1228{
1229 auto *container = popItem();
1230 container->setParent(m_rootItem);
1231 container->setParentItem(m_rootItem);
1232 container->setVisible(false);
1233 if (!info.isPatternRectRelativeCoordinates) {
1234 container->setWidth(info.patternRect.width());
1235 container->setHeight(info.patternRect.height());
1236 }
1237 m_patternDefs[info.id] = { container, info.patternRect, info.isPatternRectRelativeCoordinates };
1238}
1239
1240void QQuickItemGenerator::generatePattern(QQuickShapePath *shapePath, const PathNodeInfo &info,
1241 const QRectF &boundingRect, QTransform &fillTransform)
1242{
1243 auto it = m_patternDefs.find(info.patternId);
1244 if (it == m_patternDefs.end()) {
1245 qCWarning(lcQuickVectorImage) << "generatePattern: unknown pattern id:" << info.patternId;
1246 return;
1247 }
1248 PatternDef &patternDef = *it;
1249
1250 qreal tileW, tileH, offsetX, offsetY;
1251 if (patternDef.isPatternRectRelativeCoordinates) {
1252 tileW = patternDef.patternRect.width() * boundingRect.width();
1253 tileH = patternDef.patternRect.height() * boundingRect.height();
1254 offsetX = patternDef.patternRect.x() * boundingRect.width();
1255 offsetY = patternDef.patternRect.y() * boundingRect.height();
1256 patternDef.container->setWidth(tileW);
1257 patternDef.container->setHeight(tileH);
1258 } else {
1259 tileW = patternDef.patternRect.width();
1260 tileH = patternDef.patternRect.height();
1261 offsetX = patternDef.patternRect.x();
1262 offsetY = patternDef.patternRect.y();
1263 }
1264
1265 auto *ses = makeSES(patternDef.container, QRectF(0, 0, tileW, tileH), m_rootItem);
1266 ses->setHideSource(true);
1267 ses->setWrapMode(QQuickShaderEffectSource::Repeat);
1268 ses->setSourceRect(QRectF(0, 0, tileW, tileH));
1269 shapePath->setFillItem(ses);
1270 bindTextureSize(ses);
1271
1272 if (m_topLevelScaleSpy) {
1273 const QTransform baseTransform = fillTransform;
1274 auto *scaleSpy = m_topLevelScaleSpy;
1275 auto updateFillTransform = [shapePath, offsetX, offsetY, baseTransform, scaleSpy]() {
1276 const QSizeF unitScale = scaleSpy->requiredTextureSize();
1277 QTransform xf = baseTransform;
1278 xf.translate(offsetX, offsetY);
1279 xf.scale(1.0 / unitScale.width(), 1.0 / unitScale.height());
1280 shapePath->setFillTransform(QMatrix4x4(xf));
1281 };
1282 QObject::connect(scaleSpy, &QQuickItemSpy::requiredTextureSizeChanged, shapePath,
1283 updateFillTransform);
1284 updateFillTransform();
1285 }
1286
1287 fillTransform.translate(offsetX, offsetY);
1288}
1289
1290static qreal meanAngle(QPointF p0, QPointF p1, QPointF p2)
1291{
1292 QPointF t1 = p1 - p0;
1293 QPointF t2 = p2 - p1;
1294 qreal hyp1 = hypot(t1.x(), t1.y());
1295 if (hyp1 > 0)
1296 t1 /= hyp1;
1297 else
1298 return 0.0;
1299 qreal hyp2 = hypot(t2.x(), t2.y());
1300 if (hyp2 > 0)
1301 t2 /= hyp2;
1302 else
1303 return 0.0;
1304 QPointF tangent = t1 + t2;
1305 return -atan2(tangent.y(), tangent.x()) / M_PI * 180.0;
1306}
1307
1308void QQuickItemGenerator::generateMarkers(const PathNodeInfo &info)
1309{
1310 const QPainterPath path = info.path.defaultValue().value<QPainterPath>();
1311
1312 for (int i = 0; i < path.elementCount(); ++i) {
1313 const QPainterPath::Element element = path.elementAt(i);
1314 QString markerId;
1315 qreal angle = 0;
1316
1317 if (i == 0) {
1318 markerId = info.markerStartId;
1319 angle = path.angleAtPercent(0.0);
1320 } else if (i == path.elementCount() - 1) {
1321 markerId = info.markerEndId;
1322 angle = path.angleAtPercent(1.0);
1323 } else if (path.elementAt(i + 1).type != QPainterPath::CurveToDataElement) {
1324 markerId = info.markerMidId;
1325 QPointF p1(path.elementAt(i - 1).x, path.elementAt(i - 1).y);
1326 QPointF p2(element.x, element.y);
1327 QPointF p3(path.elementAt(i + 1).x, path.elementAt(i + 1).y);
1328 angle = meanAngle(p1, p2, p3);
1329 }
1330
1331 if (markerId.isEmpty())
1332 continue;
1333
1334 auto it = m_markerDefs.find(markerId);
1335 if (it == m_markerDefs.end()) {
1336 qCWarning(lcQuickVectorImage) << "generateMarkers: unknown marker id:" << markerId;
1337 continue;
1338 }
1339 const MarkerDef &markerDef = *it;
1340 const MarkerNodeInfo &minfo = markerDef.info;
1341
1342 const qreal sw = info.strokeStyle.width.defaultValue().toReal();
1343 const qreal markerW = minfo.markerUnits == MarkerNodeInfo::MarkerUnits::StrokeWidth
1344 ? minfo.markerSize.width() * sw
1345 : minfo.markerSize.width();
1346 const qreal markerH = minfo.markerUnits == MarkerNodeInfo::MarkerUnits::StrokeWidth
1347 ? minfo.markerSize.height() * sw
1348 : minfo.markerSize.height();
1349
1350 qreal scaleX = 1.0, scaleY = 1.0, offsetX = 0.0, offsetY = 0.0;
1351 if (minfo.viewBox.width() > 0)
1352 scaleX = markerW / minfo.viewBox.width();
1353 if (minfo.viewBox.height() > 0)
1354 scaleY = markerH / minfo.viewBox.height();
1355
1356 if (minfo.preserveAspectRatio & MarkerNodeInfo::xyMask) {
1357 if (!qFuzzyCompare(scaleX, scaleY)) {
1358 if (minfo.preserveAspectRatio & MarkerNodeInfo::meet)
1359 scaleX = scaleY = qMin(scaleX, scaleY);
1360 else
1361 scaleX = scaleY = qMax(scaleX, scaleY);
1362
1363 const qreal overflowX = scaleX * minfo.viewBox.width() - markerW;
1364 const qreal overflowY = scaleY * minfo.viewBox.height() - markerH;
1365
1366 const quint8 xRatio = minfo.preserveAspectRatio & MarkerNodeInfo::xMask;
1367 if (xRatio == MarkerNodeInfo::xMid)
1368 offsetX -= overflowX / 2;
1369 else if (xRatio == MarkerNodeInfo::xMax)
1370 offsetX -= overflowX;
1371
1372 const quint8 yRatio = minfo.preserveAspectRatio & MarkerNodeInfo::yMask;
1373 if (yRatio == MarkerNodeInfo::yMid)
1374 offsetY -= overflowY / 2;
1375 else if (yRatio == MarkerNodeInfo::yMax)
1376 offsetY -= overflowY;
1377 }
1378 }
1379
1380 const qreal anchorOffsetX = offsetX - minfo.anchorPoint.x() * scaleX;
1381 const qreal anchorOffsetY = offsetY - minfo.anchorPoint.y() * scaleY;
1382
1383 const qreal instanceAngle =
1384 minfo.orientation == MarkerNodeInfo::Orientation::Value ? minfo.angle : -angle;
1385
1386 auto *outerItem = new QQuickItem;
1387 auto outerXform = outerItem->transform();
1388 if (i == 0 && minfo.orientation == MarkerNodeInfo::Orientation::AutoStartReverse) {
1389 auto *flip = new QQuickScale(outerItem);
1390 flip->setXScale(-1);
1391 flip->setYScale(-1);
1392 outerXform.append(&outerXform, flip);
1393 }
1394 auto *rot = new QQuickRotation(outerItem);
1395 rot->setAngle(instanceAngle);
1396 outerXform.append(&outerXform, rot);
1397 auto *outerTr = new QQuickTranslate(outerItem);
1398 outerTr->setX(element.x);
1399 outerTr->setY(element.y);
1400 outerXform.append(&outerXform, outerTr);
1401 pushItem(outerItem);
1402
1403 QQuickItem *clipItem = nullptr;
1404 if (!minfo.clipBox.isEmpty()) {
1405 clipItem = new QQuickItem;
1406 const qreal unitScale =
1407 minfo.markerUnits == MarkerNodeInfo::MarkerUnits::StrokeWidth ? sw : 1.0;
1408 clipItem->setX(minfo.clipBox.x() * unitScale);
1409 clipItem->setY(minfo.clipBox.y() * unitScale);
1410 clipItem->setWidth(minfo.clipBox.width() * unitScale);
1411 clipItem->setHeight(minfo.clipBox.height() * unitScale);
1412 clipItem->setClip(true);
1413 pushItem(clipItem);
1414 }
1415
1416 auto *innerItem = new QQuickItem;
1417 auto innerXform = innerItem->transform();
1418 auto *innerScale = new QQuickScale(innerItem);
1419 innerScale->setXScale(scaleX);
1420 innerScale->setYScale(scaleY);
1421 innerXform.append(&innerXform, innerScale);
1422 auto *innerTr = new QQuickTranslate(innerItem);
1423 innerTr->setX(anchorOffsetX);
1424 innerTr->setY(anchorOffsetY);
1425 innerXform.append(&innerXform, innerTr);
1426 if (clipItem) {
1427 auto *offsetItem = new QQuickItem;
1428 const qreal unitScale =
1429 minfo.markerUnits == MarkerNodeInfo::MarkerUnits::StrokeWidth ? sw : 1.0;
1430 offsetItem->setX(-minfo.clipBox.x() * unitScale);
1431 offsetItem->setY(-minfo.clipBox.y() * unitScale);
1432 pushItem(offsetItem);
1433 }
1434 pushItem(innerItem);
1435
1436 for (const auto &step : markerDef.recording)
1437 step();
1438
1439 popItem();
1440 if (clipItem) {
1441 popItem();
1442 popItem();
1443 }
1444 popItem();
1445 }
1446}
1447
1448QT_END_NAMESPACE
Combined button and popup list for selecting options.
static qreal meanAngle(QPointF p0, QPointF p1, QPointF p2)
static QRectF resolveRect(const QRectF &rect, FilterNodeInfo::CoordinateSystem cs, const QRectF &itemBounds)
static QQuickShaderEffect * makeFilterEffect(QQuickItem *inputItem, const QUrl &shader, QQmlContext *context, QQuickItem *parent)
static QQuickShaderEffectSource * makeSES(QQuickItem *item, const QRectF &rect, QQuickItem *parent)
static QQuickShapeGradient * createShapeGradient(const QGradient &grad, const QRectF &coordSys, QObject *parent)
static QQuickShaderEffectSource * makeEffectSES(QQuickShaderEffect *effect, const QRectF &stepRect, const QRectF &filterRect, QQuickItem *parent)