handwritinggesturerecognizer.cpp 8.00 KiB
/****************************************************************************
**
** Copyright (C) 2017 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt Virtual Keyboard module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 or (at your option) any later version
** approved by the KDE Free Qt Foundation. The licenses are as published by
** the Free Software Foundation and appearing in the file LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
** $QT_END_LICENSE$
****************************************************************************/
#include "handwritinggesturerecognizer.h"
#include <QtCore/qmath.h>
#include <QVector2D>
namespace QtVirtualKeyboard {
HandwritingGestureRecognizer::HandwritingGestureRecognizer(QObject *parent) :
    GestureRecognizer(parent),
    m_dpi(96)
void HandwritingGestureRecognizer::setDpi(int value)
    m_dpi = value >= 0 ? value : 96;
int HandwritingGestureRecognizer::dpi() const
    return m_dpi;
QVariantMap HandwritingGestureRecognizer::recognize(const QList<Trace *> traceList)
    if (traceList.count() > 0 && traceList.count() < 3) {
        // Swipe gesture detection
        // =======================
        // The following algorithm is based on the assumption that a
        // vector composed of two arbitrary selected, but consecutive
        // measuring points, and a vector composed of the first and last
        // of the measuring points, are approximately in the same angle.
        // If the measuring points are located very close to each other,
        // the angle can fluctuate a lot. This has been taken into account
        // by setting a minimum Euclidean distance between the measuring
        // points.
7172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
// Minimum euclidean distance of a segment (in millimeters) static const int MINIMUM_EUCLIDEAN_DISTANCE = 8; // Maximum theta variance (in degrees) static const qreal THETA_THRESHOLD = 25.0; // Maximum width variance in multitouch swipe (+- in percent) static const int MAXIMUM_WIDTH_VARIANCE = 20; const qreal minimumEuclideanDistance = MINIMUM_EUCLIDEAN_DISTANCE / 25.4 * m_dpi; static const qreal thetaThreshold = qDegreesToRadians(THETA_THRESHOLD); QList<QVector2D> swipeVectors; int traceIndex; const int traceCount = traceList.size(); for (traceIndex = 0; traceIndex < traceCount; ++traceIndex) { const Trace *trace = traceList.at(traceIndex); const QVariantList &points = trace->points(); QVector2D swipeVector; const int pointCount = points.count(); int pointIndex = 0; if (pointCount >= 2) { QPointF startPosition = points.first().toPointF(); swipeVector = QVector2D(points.last().toPointF() - startPosition); const qreal swipeLength = swipeVector.length(); if (swipeLength >= minimumEuclideanDistance) { QPointF previousPosition = startPosition; qreal euclideanDistance = 0; for (pointIndex = 1; pointIndex < pointCount; ++pointIndex) { QPointF currentPosition(points.at(pointIndex).toPointF()); euclideanDistance += QVector2D(currentPosition - previousPosition).length(); if (euclideanDistance >= minimumEuclideanDistance) { // Set the angle (theta) between the sample vector and the swipe vector const QVector2D sampleVector(currentPosition - startPosition); const qreal theta = qAcos(QVector2D::dotProduct(swipeVector, sampleVector) / (swipeLength * sampleVector.length())); // Rejected when theta above threshold if (theta >= thetaThreshold) { swipeVector = QVector2D(); break; } startPosition = currentPosition; euclideanDistance = 0; } previousPosition = currentPosition; } if (pointIndex < pointCount) { swipeVector = QVector2D(); break; } // Check to see if angle and length matches to existing touch points if (!swipeVectors.isEmpty()) { bool matchesToExisting = true; const qreal minimumSwipeLength = (swipeLength * (100.0 - MAXIMUM_WIDTH_VARIANCE) / 100.0); const qreal maximumSwipeLength = (swipeLength * (100.0 + MAXIMUM_WIDTH_VARIANCE) / 100.0); for (const QVector2D &otherSwipeVector : qAsConst(swipeVectors)) { const qreal otherSwipeLength = otherSwipeVector.length(); const qreal theta = qAcos(QVector2D::dotProduct(swipeVector, otherSwipeVector) / (swipeLength * otherSwipeLength));
141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
if (theta >= thetaThreshold) { matchesToExisting = false; break; } if (otherSwipeLength < minimumSwipeLength || otherSwipeLength > maximumSwipeLength) { matchesToExisting = false; break; } } if (!matchesToExisting) { swipeVector = QVector2D(); break; } } } else { swipeVector = QVector2D(); } } if (swipeVector.isNull()) break; swipeVectors.append(swipeVector); } if (swipeVectors.size() == traceCount) { QVariantMap swipeGesture; // Get swipe angle from the first vector: // 0 degrees == right // 90 degrees == down // 180 degrees == left // 270 degrees == up QList<QVector2D>::ConstIterator swipeVector = swipeVectors.constBegin(); qreal swipeLength = swipeVector->length(); qreal swipeAngle = qAcos(swipeVector->x() / swipeLength); if (swipeVector->y() < 0) swipeAngle = 2 * M_PI - swipeAngle; // Calculate an average length of the vector for (++swipeVector; swipeVector != swipeVectors.end(); ++swipeVector) swipeLength += swipeVector->length(); swipeLength /= traceCount; swipeGesture[QLatin1String("type")] = QLatin1String("swipe"); swipeGesture[QLatin1String("angle")] = swipeAngle; swipeGesture[QLatin1String("angle_degrees")] = qRadiansToDegrees(swipeAngle); swipeGesture[QLatin1String("length")] = swipeLength; swipeGesture[QLatin1String("length_mm")] = swipeLength / m_dpi * 25.4; swipeGesture[QLatin1String("touch_count")] = traceCount; return swipeGesture; } } return QVariantMap(); } }