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 "qquickmaskextruder_p.h"
5#include <QtQml/qqml.h>
6#include <QtQml/qqmlinfo.h>
7#include <QtQml/qqmlcontext.h>
8
9#include <QImage>
10#include <QDebug>
11#include <QRandomGenerator>
12
13QT_BEGIN_NAMESPACE
14/*!
15 \qmltype MaskShape
16 \instantiates QQuickMaskExtruder
17 \inqmlmodule QtQuick.Particles
18 \inherits Shape
19 \brief For representing an image as a shape to affectors and emitters.
20 \ingroup qtquick-particles
21
22*/
23/*!
24 \qmlproperty url QtQuick.Particles::MaskShape::source
25
26 The image to use as the mask. Areas with non-zero opacity
27 will be considered inside the shape.
28*/
29
30
31QQuickMaskExtruder::QQuickMaskExtruder(QObject *parent) :
32 QQuickParticleExtruder(parent)
33 , m_lastWidth(-1)
34 , m_lastHeight(-1)
35{
36}
37
38void QQuickMaskExtruder::setSource(const QUrl &arg)
39{
40 if (m_source != arg) {
41 m_source = arg;
42
43 m_lastHeight = -1;//Trigger reset
44 m_lastWidth = -1;
45 emit sourceChanged(arg: m_source);
46 startMaskLoading();
47 }
48}
49
50void QQuickMaskExtruder::startMaskLoading()
51{
52 m_pix.clear(this);
53 if (m_source.isEmpty())
54 return;
55 const QQmlContext *context = qmlContext(this);
56 m_pix.load(context->engine(), context->resolvedUrl(m_source));
57 if (m_pix.isLoading())
58 m_pix.connectFinished(this, SLOT(finishMaskLoading()));
59 else
60 finishMaskLoading();
61}
62
63void QQuickMaskExtruder::finishMaskLoading()
64{
65 if (m_pix.isError())
66 qmlWarning(me: this) << m_pix.error();
67}
68
69QPointF QQuickMaskExtruder::extrude(const QRectF &r)
70{
71 ensureInitialized(r);
72 if (!m_mask.size() || m_img.isNull())
73 return r.topLeft();
74 const QPointF p = m_mask[QRandomGenerator::global()->bounded(highest: m_mask.size())];
75 //### Should random sub-pixel positioning be added?
76 return p + r.topLeft();
77}
78
79bool QQuickMaskExtruder::contains(const QRectF &bounds, const QPointF &point)
80{
81 ensureInitialized(r: bounds);//###Current usage patterns WILL lead to different bounds/r calls. Separate list?
82 if (m_img.isNull())
83 return false;
84
85 QPointF pt = point - bounds.topLeft();
86 QPoint p(pt.x() * m_img.width() / bounds.width(),
87 pt.y() * m_img.height() / bounds.height());
88 return m_img.rect().contains(p) && (m_img.pixel(pt: p) & 0xff000000);
89}
90
91void QQuickMaskExtruder::ensureInitialized(const QRectF &rf)
92{
93 // Convert to integer coords to avoid comparing floats and ints which would
94 // often result in rounding errors.
95 QRect r = rf.toRect();
96 if (m_lastWidth == r.width() && m_lastHeight == r.height())
97 return;//Same as before
98 if (!m_pix.isReady())
99 return;
100 m_lastWidth = r.width();
101 m_lastHeight = r.height();
102
103 m_mask.clear();
104
105 m_img = m_pix.image();
106 // Image will in all likelyhood be in this format already, so
107 // no extra memory or conversion takes place
108 if (m_img.format() != QImage::Format_ARGB32 && m_img.format() != QImage::Format_ARGB32_Premultiplied)
109 m_img = m_img.convertToFormat(f: QImage::Format_ARGB32_Premultiplied);
110
111 // resample on the fly using 16-bit
112 int sx = (m_img.width() << 16) / r.width();
113 int sy = (m_img.height() << 16) / r.height();
114 int w = r.width();
115 int h = r.height();
116 for (int y=0; y<h; ++y) {
117 const uint *sl = (const uint *) m_img.constScanLine((y * sy) >> 16);
118 for (int x=0; x<w; ++x) {
119 if (sl[(x * sx) >> 16] & 0xff000000)
120 m_mask << QPointF(x, y);
121 }
122 }
123}
124QT_END_NAMESPACE
125
126#include "moc_qquickmaskextruder_p.cpp"
127

source code of qtdeclarative/src/particles/qquickmaskextruder.cpp