Commit f3d76c6c authored by Dmitry Kazakov's avatar Dmitry Kazakov
Browse files

[FEATURE] Make brush 'speed' sensor work

Implemented a complex smoothing algorithm for speed sensor,
which makes Speed work almost perfectly.

The smoothing is two-stage:
1) Calculate the distance of the path connecting last 10 points
   (but not longer than 300px).
2) Divide the distance by time value and smooth the result further
   with 10 previous values

BUG:325423
Fixes:T426
CC:kimageshop@kde.org
parent 411067b1
......@@ -30,7 +30,32 @@
struct KisPaintInformation::Private {
EIGEN_MAKE_ALIGNED_OPERATOR_NEW
Private() : currentDistanceInfo(0) {}
Private(const QPointF & pos_,
qreal pressure_,
qreal xTilt_, qreal yTilt_,
qreal rotation_,
qreal tangentialPressure_,
qreal perspective_,
qreal time_,
qreal speed_,
bool isHoveringMode_)
:
pos(pos_),
pressure(pressure_),
xTilt(xTilt_),
yTilt(yTilt_),
rotation(rotation_),
tangentialPressure(tangentialPressure_),
perspective(perspective_),
time(time_),
speed(speed_),
isHoveringMode(isHoveringMode_),
currentDistanceInfo(0)
{
}
~Private() {
KIS_ASSERT_RECOVER_NOOP(!currentDistanceInfo);
}
......@@ -51,6 +76,7 @@ struct KisPaintInformation::Private {
tangentialPressure = rhs.tangentialPressure;
perspective = rhs.perspective;
time = rhs.time;
speed = rhs.speed;
isHoveringMode = rhs.isHoveringMode;
currentDistanceInfo = rhs.currentDistanceInfo;
......@@ -68,6 +94,7 @@ struct KisPaintInformation::Private {
qreal tangentialPressure;
qreal perspective;
qreal time;
qreal speed;
bool isHoveringMode;
QScopedPointer<qreal> drawingAngleOverride;
......@@ -95,27 +122,60 @@ KisPaintInformation::DistanceInformationRegistrar::
p->d->unregisterDistanceInfo();
}
KisPaintInformation::KisPaintInformation(const QPointF & pos_,
qreal pressure_,
qreal xTilt_, qreal yTilt_,
qreal rotation_,
qreal tangentialPressure_,
qreal perspective_,
qreal time)
: d(new Private)
KisPaintInformation::KisPaintInformation(const QPointF & pos,
qreal pressure,
qreal xTilt, qreal yTilt,
qreal rotation,
qreal tangentialPressure,
qreal perspective,
qreal time,
qreal speed)
: d(new Private(pos,
pressure,
xTilt, yTilt,
rotation,
tangentialPressure,
perspective,
time,
speed,
false))
{
}
KisPaintInformation::KisPaintInformation(const QPointF & pos,
qreal pressure,
qreal xTilt,
qreal yTilt,
qreal rotation)
: d(new Private(pos,
pressure,
xTilt, yTilt,
rotation,
0.0,
1.0,
0.0,
0.0,
false))
{
}
KisPaintInformation::KisPaintInformation(const QPointF &pos,
qreal pressure)
: d(new Private(pos,
pressure,
0.0, 0.0,
0.0,
0.0,
1.0,
0.0,
0.0,
false))
{
d->pos = pos_;
d->pressure = pressure_;
d->xTilt = xTilt_;
d->yTilt = yTilt_;
d->rotation = rotation_;
d->tangentialPressure = tangentialPressure_;
d->perspective = perspective_;
d->time = time;
d->isHoveringMode = false;
}
KisPaintInformation::KisPaintInformation(const KisPaintInformation& rhs) : d(new Private(*rhs.d))
KisPaintInformation::KisPaintInformation(const KisPaintInformation& rhs)
: d(new Private(*rhs.d))
{
}
......@@ -140,14 +200,15 @@ KisPaintInformation::createHoveringModeInfo(const QPointF &pos,
qreal xTilt, qreal yTilt,
qreal rotation,
qreal tangentialPressure,
qreal perspective)
qreal perspective,
qreal speed)
{
KisPaintInformation info(pos,
pressure,
xTilt, yTilt,
rotation,
tangentialPressure,
perspective, 0);
perspective, 0, speed);
info.d->isHoveringMode = true;
return info;
}
......@@ -166,6 +227,7 @@ void KisPaintInformation::toXML(QDomDocument&, QDomElement& e) const
e.setAttribute("tangentialPressure", QString::number(tangentialPressure(), 'g', 15));
e.setAttribute("perspective", QString::number(perspective(), 'g', 15));
e.setAttribute("time", d->time);
e.setAttribute("speed", d->speed);
}
KisPaintInformation KisPaintInformation::fromXML(const QDomElement& e)
......@@ -179,9 +241,10 @@ KisPaintInformation KisPaintInformation::fromXML(const QDomElement& e)
qreal xTilt = qreal(e.attribute("xTilt", "0.0").toDouble());
qreal yTilt = qreal(e.attribute("yTilt", "0.0").toDouble());
qreal time = e.attribute("time", "0").toDouble();
qreal speed = e.attribute("speed", "0").toDouble();
return KisPaintInformation(QPointF(pointX, pointY), pressure, xTilt, yTilt,
rotation, tangentialPressure, perspective, time);
rotation, tangentialPressure, perspective, time, speed);
}
const QPointF& KisPaintInformation::pos() const
......@@ -275,19 +338,7 @@ qreal KisPaintInformation::drawingDistance() const
qreal KisPaintInformation::drawingSpeed() const
{
if (!d->currentDistanceInfo || !d->currentDistanceInfo->hasLastDabInformation()) {
qWarning() << "KisPaintInformation::drawingSpeed()" << "Cannot access Distance Info last dab data";
return 0.5;
}
qreal timeDiff = currentTime() - d->currentDistanceInfo->lastTime();
if (timeDiff <= 0) {
return 0.5;
}
QVector2D diff(pos() - d->currentDistanceInfo->lastPosition());
return diff.length() / timeDiff;
return d->speed;
}
qreal KisPaintInformation::rotation() const
......@@ -340,7 +391,8 @@ KisPaintInformation KisPaintInformation::mixOnlyPosition(qreal t, const KisPaint
basePi.rotation(),
basePi.tangentialPressure(),
basePi.perspective(),
basePi.currentTime());
basePi.currentTime(),
basePi.drawingSpeed());
return result;
}
......@@ -370,8 +422,9 @@ KisPaintInformation KisPaintInformation::mix(const QPointF& p, qreal t, const Ki
qreal tangentialPressure = (1 - t) * pi1.tangentialPressure() + t * pi2.tangentialPressure();
qreal perspective = (1 - t) * pi1.perspective() + t * pi2.perspective();
qreal time = (1 - t) * pi1.currentTime() + t * pi2.currentTime();
qreal speed = (1 - t) * pi1.drawingSpeed() + t * pi2.drawingSpeed();
KisPaintInformation result(p, pressure, xTilt, yTilt, rotation, tangentialPressure, perspective, time);
KisPaintInformation result(p, pressure, xTilt, yTilt, rotation, tangentialPressure, perspective, time, speed);
KIS_ASSERT_RECOVER_NOOP(pi1.isHoveringMode() == pi2.isHoveringMode());
result.d->isHoveringMode = pi1.isHoveringMode();
......
......@@ -75,16 +75,25 @@ public:
/**
* Create a new KisPaintInformation object.
*/
KisPaintInformation(const QPointF & pos,
qreal pressure,
qreal xTilt,
qreal yTilt,
qreal rotation,
qreal tangentialPressure,
qreal perspective,
qreal time,
qreal speed);
KisPaintInformation(const QPointF & pos,
qreal pressure,
qreal xTilt,
qreal yTilt,
qreal rotation);
KisPaintInformation(const QPointF & pos = QPointF(),
qreal pressure = PRESSURE_DEFAULT,
qreal xTilt = 0.0,
qreal yTilt = 0.0,
qreal rotation = 0.0,
qreal tangentialPressure = 0.0,
qreal perspective = 1.0,
qreal time = 0.0);
qreal pressure = PRESSURE_DEFAULT);
KisPaintInformation(const KisPaintInformation& rhs);
......@@ -198,7 +207,8 @@ public:
qreal xTilt = 0.0, qreal yTilt = 0.0,
qreal rotation = 0.0,
qreal tangentialPressure = 0.0,
qreal perspective = 1.0);
qreal perspective = 1.0,
qreal speed = 0.0);
void toXML(QDomDocument&, QDomElement&) const;
......
......@@ -34,7 +34,7 @@ void KisPaintInformationTest::testCreation()
void KisPaintInformationTest::testSerialisation()
{
KisPaintInformation test(QPointF(double(rand()) / RAND_MAX, double(rand()) / RAND_MAX), double(rand()) / RAND_MAX, double(rand()) / RAND_MAX, double(rand()) / RAND_MAX, double(rand()) / RAND_MAX, double(rand()) / RAND_MAX);
KisPaintInformation test(QPointF(double(rand()) / RAND_MAX, double(rand()) / RAND_MAX), double(rand()) / RAND_MAX, double(rand()) / RAND_MAX, double(rand()) / RAND_MAX, double(rand()) / RAND_MAX, double(rand()) / RAND_MAX, double(rand()) / RAND_MAX, double(rand()) / RAND_MAX, double(rand()) / RAND_MAX);
QDomDocument doc = QDomDocument("pi");
QDomElement root = doc.createElement("pi");
......
......@@ -294,7 +294,7 @@ QImage KisAutoBrush::createBrushPreview()
int width = maskWidth(1.0, 0.0, 0.0, 0.0, KisPaintInformation());
int height = maskHeight(1.0, 0.0, 0.0, 0.0, KisPaintInformation());
KisPaintInformation info(QPointF(width * 0.5, height * 0.5), 0.5, 0, angle(), 0, 0);
KisPaintInformation info(QPointF(width * 0.5, height * 0.5), 0.5, 0, 0, angle(), 0, 0, 0, 0);
KisFixedPaintDeviceSP fdev = new KisFixedPaintDevice(KoColorSpaceRegistry::instance()->rgb8());
fdev->setRect(QRect(0, 0, width, height));
......
......@@ -44,7 +44,7 @@ void KisAutoBrushTest::testMaskGeneration()
KisBrushSP a = new KisAutoBrush(circle, 0.0, 0.0);
const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8();
KisPaintInformation info(QPointF(100.0, 100.0), 0.5, 0, 0, 0, 0);
KisPaintInformation info(QPointF(100.0, 100.0), 0.5);
// check masking an existing paint device
KisFixedPaintDeviceSP fdev = new KisFixedPaintDevice(cs);
......
......@@ -39,7 +39,7 @@ void KisBrushTest::testMaskGenerationNoColor()
Q_ASSERT(brush->valid());
const KoColorSpace* cs = KoColorSpaceRegistry::instance()->rgb8();
KisPaintInformation info(QPointF(100.0, 100.0), 0.5, 0, 0, 0);
KisPaintInformation info(QPointF(100.0, 100.0), 0.5);
// check masking an existing paint device
KisFixedPaintDeviceSP fdev = new KisFixedPaintDevice(cs);
......@@ -73,7 +73,7 @@ void KisBrushTest::testMaskGenerationSingleColor()
Q_ASSERT(brush->valid());
const KoColorSpace* cs = KoColorSpaceRegistry::instance()->rgb8();
KisPaintInformation info(QPointF(100.0, 100.0), 0.5, 0, 0, 0, 0);
KisPaintInformation info(QPointF(100.0, 100.0), 0.5);
// check masking an existing paint device
KisFixedPaintDeviceSP fdev = new KisFixedPaintDevice(cs);
......@@ -101,7 +101,7 @@ void KisBrushTest::testMaskGenerationDevColor()
Q_ASSERT(brush->valid());
const KoColorSpace* cs = KoColorSpaceRegistry::instance()->rgb8();
KisPaintInformation info(QPointF(100.0, 100.0), 0.5, 0, 0, 0, 0);
KisPaintInformation info(QPointF(100.0, 100.0), 0.5);
// check masking an existing paint device
KisFixedPaintDeviceSP fdev = new KisFixedPaintDevice(cs);
......@@ -134,7 +134,7 @@ void KisBrushTest::testMaskGenerationDefaultColor()
Q_ASSERT(brush->valid());
const KoColorSpace* cs = KoColorSpaceRegistry::instance()->rgb8();
KisPaintInformation info(QPointF(100.0, 100.0), 0.5, 0, 0, 0, 0);
KisPaintInformation info(QPointF(100.0, 100.0), 0.5);
// check masking an existing paint device
KisFixedPaintDeviceSP fdev = new KisFixedPaintDevice(cs);
......@@ -169,7 +169,7 @@ void KisBrushTest::testImageGeneration()
qsrand(1);
const KoColorSpace* cs = KoColorSpaceRegistry::instance()->rgb8();
KisPaintInformation info(QPointF(100.0, 100.0), 0.5, 0, 0, 0, 0);
KisPaintInformation info(QPointF(100.0, 100.0), 0.5);
KisFixedPaintDeviceSP dab;
for (int i = 0; i < 200; i++) {
......@@ -213,7 +213,7 @@ void KisBrushTest::benchmarkScaling()
qsrand(1);
const KoColorSpace* cs = KoColorSpaceRegistry::instance()->rgb8();
KisPaintInformation info(QPointF(100.0, 100.0), 0.5, 0, 0, 0, 0);
KisPaintInformation info(QPointF(100.0, 100.0), 0.5);
KisFixedPaintDeviceSP dab;
QBENCHMARK {
......@@ -231,7 +231,7 @@ void KisBrushTest::benchmarkRotation()
qsrand(1);
const KoColorSpace* cs = KoColorSpaceRegistry::instance()->rgb8();
KisPaintInformation info(QPointF(100.0, 100.0), 0.5, 0, 0, 0, 0);
KisPaintInformation info(QPointF(100.0, 100.0), 0.5);
KisFixedPaintDeviceSP dab;
QBENCHMARK {
......@@ -248,7 +248,7 @@ void KisBrushTest::benchmarkMaskScaling()
qsrand(1);
const KoColorSpace* cs = KoColorSpaceRegistry::instance()->rgb8();
KisPaintInformation info(QPointF(100.0, 100.0), 0.5, 0, 0, 0, 0);
KisPaintInformation info(QPointF(100.0, 100.0), 0.5);
KisFixedPaintDeviceSP dab = new KisFixedPaintDevice(cs);
QBENCHMARK {
......
......@@ -110,7 +110,7 @@ void KisImagePipeBrushTest::testChangingBrushes()
QVERIFY(brush->valid());
qreal rotation = 0;
KisPaintInformation info(QPointF(100.0, 100.0), 0.5, 0, 0, rotation, 0);
KisPaintInformation info(QPointF(100.0, 100.0), 0.5, 0, 0, rotation);
for (int i = 0; i < 100; i++) {
checkConsistency(brush);
......@@ -133,7 +133,7 @@ void checkIncrementalPainting(KisBrush *brush, const QString &prefix)
qreal rotation = 0;
qreal subPixelX = 0.0;
qreal subPixelY = 0.0;
KisPaintInformation info(QPointF(100.0, 100.0), 0.5, 0, 0, rotation, 0);
KisPaintInformation info(QPointF(100.0, 100.0), 0.5, 0, 0, rotation);
for (int i = 0; i < 20; i++) {
int maskWidth = brush->maskWidth(realScale, realAngle, subPixelX, subPixelY, info);
......@@ -210,7 +210,7 @@ void KisImagePipeBrushTest::testColoredDabWash()
const KoColorSpace* cs = KoColorSpaceRegistry::instance()->rgb8();
qreal rotation = 0;
KisPaintInformation info(QPointF(100.0, 100.0), 0.5, 0, 0, rotation, 0);
KisPaintInformation info(QPointF(100.0, 100.0), 0.5, 0, 0, rotation);
KisPaintDeviceSP layer = new KisPaintDevice(cs);
KisPainter painter(layer);
......
......@@ -23,14 +23,14 @@
KisSensorsTest::KisSensorsTest()
{
paintInformations.append(KisPaintInformation(QPointF(0, 0), 0, 0, 0, 0.0, 0.0, 1.0));
paintInformations.append(KisPaintInformation(QPointF(0, 1), 0, 0, 0, 0.0, 0.0, 1.0));
paintInformations.append(KisPaintInformation(QPointF(1, 2), 0, 0, 0, 0.0, 0.0, 1.0));
paintInformations.append(KisPaintInformation(QPointF(2, 2), 0, 0, 0, 0.0, 0.0, 1.0));
paintInformations.append(KisPaintInformation(QPointF(3, 1), 0, 0, 0, 0.0, 0.0, 1.0));
paintInformations.append(KisPaintInformation(QPointF(3, 0), 0, 0, 0, 0.0, 0.0, 1.0));
paintInformations.append(KisPaintInformation(QPointF(2, -1), 0, 0, 0, 0.0, 0.0, 1.0));
paintInformations.append(KisPaintInformation(QPointF(1, -1), 0, 0, 0, 0.0, 0.0, 1.0));
paintInformations.append(KisPaintInformation(QPointF(0, 0)));
paintInformations.append(KisPaintInformation(QPointF(0, 1)));
paintInformations.append(KisPaintInformation(QPointF(1, 2)));
paintInformations.append(KisPaintInformation(QPointF(2, 2)));
paintInformations.append(KisPaintInformation(QPointF(3, 1)));
paintInformations.append(KisPaintInformation(QPointF(3, 0)));
paintInformations.append(KisPaintInformation(QPointF(2, -1)));
paintInformations.append(KisPaintInformation(QPointF(1, -1)));
}
void KisSensorsTest::testDrawingAngle()
......
......@@ -170,6 +170,7 @@ set(kritaui_LIB_SRCS
tool/kis_tool.cc
tool/kis_delegated_tool_policies.cpp
tool/kis_tool_freehand.cc
tool/kis_speed_smoother.cpp
tool/kis_painting_information_builder.cpp
tool/kis_tool_freehand_helper.cpp
tool/kis_tool_multihand_helper.cpp
......
......@@ -24,6 +24,7 @@
#include "kis_config_notifier.h"
#include "kis_cubic_curve.h"
#include "kis_speed_smoother.h"
/***********************************************************************/
......@@ -35,6 +36,7 @@ const int KisPaintingInformationBuilder::LEVEL_OF_PRESSURE_RESOLUTION = 1024;
KisPaintingInformationBuilder::KisPaintingInformationBuilder()
: m_speedSmoother(new KisSpeedSmoother())
{
connect(KisConfigNotifier::instance(), SIGNAL(configChanged()),
SLOT(updateSettings()));
......@@ -42,6 +44,11 @@ KisPaintingInformationBuilder::KisPaintingInformationBuilder()
updateSettings();
}
KisPaintingInformationBuilder::~KisPaintingInformationBuilder()
{
}
void KisPaintingInformationBuilder::updateSettings()
{
KisConfig cfg;
......@@ -74,6 +81,11 @@ QPointF KisPaintingInformationBuilder::documentToImage(const QPointF &point)
return point;
}
QPointF KisPaintingInformationBuilder::imageToView(const QPointF &point)
{
return point;
}
qreal KisPaintingInformationBuilder::calculatePerspective(const QPointF &documentPoint)
{
Q_UNUSED(documentPoint);
......@@ -88,6 +100,7 @@ KisPaintInformation KisPaintingInformationBuilder::createPaintingInformation(KoP
QPointF adjusted = adjustDocumentPoint(event->point, m_startPoint);
QPointF imagePoint = documentToImage(adjusted);
qreal perspective = calculatePerspective(adjusted);
qreal speed = m_speedSmoother->getNextSpeed(imageToView(imagePoint));
return KisPaintInformation(imagePoint,
pressureToCurve(event->pressure()),
......@@ -95,13 +108,15 @@ KisPaintInformation KisPaintingInformationBuilder::createPaintingInformation(KoP
event->rotation(),
event->tangentialPressure(),
perspective,
timeElapsed);
timeElapsed,
speed);
}
KisPaintInformation KisPaintingInformationBuilder::hover(const QPointF &imagePoint,
const KoPointerEvent *event)
{
qreal perspective = calculatePerspective(imagePoint);
qreal speed = m_speedSmoother->getNextSpeed(imageToView(imagePoint));
if (event) {
return KisPaintInformation::createHoveringModeInfo(imagePoint,
......@@ -109,7 +124,8 @@ KisPaintInformation KisPaintingInformationBuilder::hover(const QPointF &imagePoi
event->xTilt(), event->yTilt(),
event->rotation(),
event->tangentialPressure(),
perspective);
perspective,
speed);
} else {
return KisPaintInformation::createHoveringModeInfo(imagePoint);
}
......@@ -136,6 +152,11 @@ QPointF KisConverterPaintingInformationBuilder::documentToImage(const QPointF &p
return m_converter->documentToImage(point);
}
QPointF KisConverterPaintingInformationBuilder::imageToView(const QPointF &point)
{
return m_converter->documentToWidget(point);
}
/***********************************************************************/
/* KisToolFreehandPaintingInformationBuilder */
/***********************************************************************/
......@@ -152,6 +173,11 @@ QPointF KisToolFreehandPaintingInformationBuilder::documentToImage(const QPointF
return m_tool->convertToPixelCoord(point);
}
QPointF KisToolFreehandPaintingInformationBuilder::imageToView(const QPointF &point)
{
return m_tool->pixelToView(point);
}
QPointF KisToolFreehandPaintingInformationBuilder::adjustDocumentPoint(const QPointF &point, const QPointF &startPoint)
{
return m_tool->adjustPosition(point, startPoint);
......
......@@ -30,7 +30,7 @@ class KoPointerEvent;
class KisTool;
class KisToolFreehand;
class KisCoordinatesConverter;
class KisSpeedSmoother;
class KRITAUI_EXPORT KisPaintingInformationBuilder : public QObject
{
......@@ -38,6 +38,7 @@ class KRITAUI_EXPORT KisPaintingInformationBuilder : public QObject
public:
KisPaintingInformationBuilder();
~KisPaintingInformationBuilder();
KisPaintInformation startStroke(KoPointerEvent *event, int timeElapsed);
......@@ -53,6 +54,7 @@ protected Q_SLOTS:
protected:
virtual QPointF adjustDocumentPoint(const QPointF &point, const QPointF &startPoint);
virtual QPointF documentToImage(const QPointF &point);
virtual QPointF imageToView(const QPointF &point);
virtual qreal calculatePerspective(const QPointF &documentPoint);
private:
......@@ -70,6 +72,7 @@ private:
private:
QVector<qreal> m_pressureSamples;
QPointF m_startPoint;
QScopedPointer<KisSpeedSmoother> m_speedSmoother;
};
class KRITAUI_EXPORT KisConverterPaintingInformationBuilder : public KisPaintingInformationBuilder
......@@ -81,6 +84,7 @@ public:
protected:
virtual QPointF documentToImage(const QPointF &point);
virtual QPointF imageToView(const QPointF &point);
private:
const KisCoordinatesConverter *m_converter;
......@@ -95,6 +99,7 @@ public:
protected:
virtual QPointF documentToImage(const QPointF &point);
virtual QPointF imageToView(const QPointF &point);
virtual QPointF adjustDocumentPoint(const QPointF &point, const QPointF &startPoint);
virtual qreal calculatePerspective(const QPointF &documentPoint);
......
/*
* Copyright (c) 2015 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_speed_smoother.h"
#include <boost/circular_buffer.hpp>
#include <QElapsedTimer>
#include <QPointF>
#include "kis_debug.h"
#include "kis_global.h"
#define MAX_SMOOTH_HISTORY 10
#define MAX_TIME_DIFF 500
#define MAX_TRACKING_DISTANCE 300
#define MIN_TRACKING_DISTANCE 5
struct KisSpeedSmoother::Private
{
Private(int historySize)
: distances(historySize),
lastSpeed(0)
{
timer.start();