1/****************************************************************************
2**
3** Copyright (C) 2017 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the Qt Virtual Keyboard module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:GPL$
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 General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU
19** General Public License version 3 or (at your option) any later version
20** approved by the KDE Free Qt Foundation. The licenses are as published by
21** the Free Software Foundation and appearing in the file LICENSE.GPL3
22** included in the packaging of this file. Please review the following
23** information to ensure the GNU General Public License requirements will
24** be met: https://www.gnu.org/licenses/gpl-3.0.html.
25**
26** $QT_END_LICENSE$
27**
28****************************************************************************/
29
30#include <QtVirtualKeyboard/private/handwritinggesturerecognizer_p.h>
31
32#include <QtCore/qmath.h>
33#include <QVector2D>
34
35QT_BEGIN_NAMESPACE
36namespace QtVirtualKeyboard {
37
38HandwritingGestureRecognizer::HandwritingGestureRecognizer(QObject *parent) :
39 GestureRecognizer(parent),
40 m_dpi(96)
41{
42}
43
44void HandwritingGestureRecognizer::setDpi(int value)
45{
46 m_dpi = value >= 0 ? value : 96;
47}
48
49int HandwritingGestureRecognizer::dpi() const
50{
51 return m_dpi;
52}
53
54QVariantMap HandwritingGestureRecognizer::recognize(const QList<QVirtualKeyboardTrace *> traceList)
55{
56 if (traceList.count() > 0 && traceList.count() < 3) {
57
58 // Swipe gesture detection
59 // =======================
60 //
61 // The following algorithm is based on the assumption that a
62 // vector composed of two arbitrary selected, but consecutive
63 // measuring points, and a vector composed of the first and last
64 // of the measuring points, are approximately in the same angle.
65 //
66 // If the measuring points are located very close to each other,
67 // the angle can fluctuate a lot. This has been taken into account
68 // by setting a minimum Euclidean distance between the measuring
69 // points.
70 //
71
72 // Minimum euclidean distance of a segment (in millimeters)
73 static const int MINIMUM_EUCLIDEAN_DISTANCE = 8;
74
75 // Maximum theta variance (in degrees)
76 static const qreal THETA_THRESHOLD = 25.0;
77
78 // Maximum width variance in multitouch swipe (+- in percent)
79 static const int MAXIMUM_WIDTH_VARIANCE = 20;
80
81 const qreal minimumEuclideanDistance = MINIMUM_EUCLIDEAN_DISTANCE / 25.4 * m_dpi;
82 static const qreal thetaThreshold = qDegreesToRadians(degrees: THETA_THRESHOLD);
83
84 QList<QVector2D> swipeVectors;
85
86 int traceIndex;
87 const int traceCount = traceList.size();
88 for (traceIndex = 0; traceIndex < traceCount; ++traceIndex) {
89
90 const QVirtualKeyboardTrace *trace = traceList.at(i: traceIndex);
91 const QVariantList &points = trace->points();
92 QVector2D swipeVector;
93 const int pointCount = points.count();
94 int pointIndex = 0;
95 if (pointCount >= 2) {
96
97 QPointF startPosition = points.first().toPointF();
98 swipeVector = QVector2D(points.last().toPointF() - startPosition);
99 const qreal swipeLength = swipeVector.length();
100
101 if (swipeLength >= minimumEuclideanDistance) {
102
103 QPointF previousPosition = startPosition;
104 qreal euclideanDistance = 0;
105 for (pointIndex = 1; pointIndex < pointCount; ++pointIndex) {
106
107 QPointF currentPosition(points.at(i: pointIndex).toPointF());
108
109 euclideanDistance += QVector2D(currentPosition - previousPosition).length();
110 if (euclideanDistance >= minimumEuclideanDistance) {
111
112 // Set the angle (theta) between the sample vector and the swipe vector
113 const QVector2D sampleVector(currentPosition - startPosition);
114 const qreal theta = qAcos(v: QVector2D::dotProduct(v1: swipeVector, v2: sampleVector) / (swipeLength * sampleVector.length()));
115
116 // Rejected when theta above threshold
117 if (theta >= thetaThreshold) {
118 swipeVector = QVector2D();
119 break;
120 }
121
122 startPosition = currentPosition;
123 euclideanDistance = 0;
124 }
125
126 previousPosition = currentPosition;
127 }
128
129 if (pointIndex < pointCount) {
130 swipeVector = QVector2D();
131 break;
132 }
133
134 // Check to see if angle and length matches to existing touch points
135 if (!swipeVectors.isEmpty()) {
136 bool matchesToExisting = true;
137 const qreal minimumSwipeLength = (swipeLength * (100.0 - MAXIMUM_WIDTH_VARIANCE) / 100.0);
138 const qreal maximumSwipeLength = (swipeLength * (100.0 + MAXIMUM_WIDTH_VARIANCE) / 100.0);
139 for (const QVector2D &otherSwipeVector : qAsConst(t&: swipeVectors)) {
140 const qreal otherSwipeLength = otherSwipeVector.length();
141 const qreal theta = qAcos(v: QVector2D::dotProduct(v1: swipeVector, v2: otherSwipeVector) / (swipeLength * otherSwipeLength));
142
143 if (theta >= thetaThreshold) {
144 matchesToExisting = false;
145 break;
146 }
147
148 if (otherSwipeLength < minimumSwipeLength || otherSwipeLength > maximumSwipeLength) {
149 matchesToExisting = false;
150 break;
151 }
152 }
153
154 if (!matchesToExisting) {
155 swipeVector = QVector2D();
156 break;
157 }
158 }
159 } else {
160 swipeVector = QVector2D();
161 }
162 }
163
164 if (swipeVector.isNull())
165 break;
166
167 swipeVectors.append(t: swipeVector);
168 }
169
170 if (swipeVectors.size() == traceCount) {
171
172 QVariantMap swipeGesture;
173
174 // Get swipe angle from the first vector:
175 // 0 degrees == right
176 // 90 degrees == down
177 // 180 degrees == left
178 // 270 degrees == up
179 QList<QVector2D>::ConstIterator swipeVector = swipeVectors.constBegin();
180 qreal swipeLength = swipeVector->length();
181 qreal swipeAngle = qAcos(v: swipeVector->x() / swipeLength);
182 if (swipeVector->y() < 0)
183 swipeAngle = 2 * M_PI - swipeAngle;
184
185 // Calculate an average length of the vector
186 ++swipeVector;
187 for (const auto cend = swipeVectors.cend(); swipeVector != cend; ++swipeVector)
188 swipeLength += swipeVector->length();
189 swipeLength /= traceCount;
190
191 swipeGesture[QLatin1String("type")] = QLatin1String("swipe");
192 swipeGesture[QLatin1String("angle")] = swipeAngle;
193 swipeGesture[QLatin1String("angle_degrees")] = qRadiansToDegrees(radians: swipeAngle);
194 swipeGesture[QLatin1String("length")] = swipeLength;
195 swipeGesture[QLatin1String("length_mm")] = swipeLength / m_dpi * 25.4;
196 swipeGesture[QLatin1String("touch_count")] = traceCount;
197
198 return swipeGesture;
199 }
200 }
201
202 return QVariantMap();
203}
204
205} // namespace QtVirtualKeyboard
206QT_END_NAMESPACE
207

source code of qtvirtualkeyboard/src/virtualkeyboard/handwritinggesturerecognizer.cpp