Commit 327f800d authored by Dmitry Kazakov's avatar Dmitry Kazakov
Browse files

Fixed Speed, Distance and Drawing Angle sensors

This patch makes the following user-visible changes:

1) Speed and Distance sensors are fixed
2) Drawing Angle sensor became much more stable and smooth
3) Drawing Angle sensor works right on the rotated canvas

Technical details:

This patch hugely refactors the distance information part of
our painting system. The two most significant changes:

1) KisPaintInformation doesn't have movement() method, because it
   used to get inconsistent *very* fast when the lien was split into
   chunks in paintBezierCurve. The access to drawingDistance(),
   drawingAngle() and drawingSpeep() parameters is provided only by means
   of teh associated KisDistanceInformation.

2) (follows from the point 1) From now on paintAt() function also accepts
    KisDistanceInformation object, because otherwise it will not be able
    to access the calculated parameters.

BUG:321771,308408,279442
parent fca69098
......@@ -131,12 +131,14 @@ void KisLowMemoryBenchmark::benchmarkWideArea(const QString presetFileName,
painter->beginTransaction("test transaction");
}
KisDistanceInformation currentDistance;
while(line.y1() < rectBottom) {
lineTime.restart();
KisPaintInformation pi1(line.p1(), 0.0);
KisPaintInformation pi2(line.p2(), 1.0);
painter->paintLine(pi1, pi2);
painter->paintLine(pi1, pi2, &currentDistance);
logStream << "L 1" << i << lineTime.elapsed()
<< KisTileDataStore::instance()->numTilesInMemory() * 16
......
......@@ -369,11 +369,12 @@ inline void KisStrokeBenchmark::benchmarkLine(QString presetFileName)
QPointF startPoint(0.10 * TEST_IMAGE_WIDTH, 0.5 * TEST_IMAGE_HEIGHT);
QPointF endPoint(0.90 * TEST_IMAGE_WIDTH, 0.5 * TEST_IMAGE_HEIGHT);
KisDistanceInformation currentDistance;
KisPaintInformation pi1(startPoint, 0.0);
KisPaintInformation pi2(endPoint, 1.0);
QBENCHMARK{
m_painter->paintLine(pi1,pi2);
m_painter->paintLine(pi1, pi2, &currentDistance);
}
#ifdef SAVE_OUTPUT
......@@ -408,26 +409,25 @@ QBENCHMARK{
QPointF first(center.x()+radius,center.y());
srand48(0);
for (int k = 0; k < rounds; k++){
m_painter->paintLine(center, first);
QPointF prev = first;
for (int i = 1; i < steps; i++) {
qreal cx = cos(i * step * 2 * M_PI);
qreal cy = sin(i * step * 2 * M_PI);
cx *= (radius + drand48() * randomOffset);
cy *= (radius + drand48() * randomOffset);
cx += center.x();
cy += center.y();
m_painter->paintLine(prev, QPointF(cx,cy));
prev = QPointF(cx,cy);
for (int k = 0; k < rounds; k++){
KisDistanceInformation currentDistance;
m_painter->paintLine(center, first, &currentDistance);
QPointF prev = first;
for (int i = 1; i < steps; i++) {
qreal cx = cos(i * step * 2 * M_PI);
qreal cy = sin(i * step * 2 * M_PI);
cx *= (radius + drand48() * randomOffset);
cy *= (radius + drand48() * randomOffset);
cx += center.x();
cy += center.y();
m_painter->paintLine(prev, QPointF(cx,cy), &currentDistance);
prev = QPointF(cx,cy);
}
m_painter->paintLine(prev, first, &currentDistance);
}
m_painter->paintLine(prev, first);
}
}
#ifdef SAVE_OUTPUT
......@@ -453,10 +453,11 @@ void KisStrokeBenchmark::benchmarkRandomLines(QString presetFileName)
m_painter->setPaintOpPreset(preset, m_image);
QBENCHMARK{
KisDistanceInformation currentDistance;
for (int i = 0; i < LINES; i++){
KisPaintInformation pi1(m_startPoints[i], 0.0);
KisPaintInformation pi2(m_endPoints[i], 1.0);
m_painter->paintLine(pi1, pi2);
m_painter->paintLine(pi1, pi2, &currentDistance);
}
}
......@@ -480,8 +481,9 @@ void KisStrokeBenchmark::benchmarkStroke(QString presetFileName)
m_painter->setPaintOpPreset(preset, m_image);
QBENCHMARK{
m_painter->paintBezierCurve(m_pi1, m_c1, m_c1, m_pi2, KisDistanceInformation());
m_painter->paintBezierCurve(m_pi2, m_c2, m_c2, m_pi3, KisDistanceInformation());
KisDistanceInformation currentDistance;
m_painter->paintBezierCurve(m_pi1, m_c1, m_c1, m_pi2, &currentDistance);
m_painter->paintBezierCurve(m_pi2, m_c2, m_c2, m_pi3, &currentDistance);
}
#ifdef SAVE_OUTPUT
......
......@@ -17,41 +17,54 @@
*/
#include "kis_paint_information.h"
#include <QDomElement>
#include "kis_paintop.h"
#include "kis_distance_information.h"
struct KisPaintInformation::Private {
EIGEN_MAKE_ALIGNED_OPERATOR_NEW
Private() : currentDistanceInfo(0) {}
QPointF pos;
qreal pressure;
qreal xTilt;
qreal yTilt;
KisVector2D movement;
qreal angle;
qreal rotation;
qreal tangentialPressure;
qreal perspective;
int time;
KisDistanceInformation *currentDistanceInfo;
void registerDistanceInfo(KisDistanceInformation *di) {
currentDistanceInfo = di;
}
void unregisterDistanceInfo() {
currentDistanceInfo = 0;
}
};
KisPaintInformation::KisPaintInformation(const QPointF & pos_, qreal pressure_,
qreal xTilt_, qreal yTilt_,
const KisVector2D& movement_,
qreal rotation_,
qreal tangentialPressure_,
qreal perspective_,
int time)
: d(new Private)
KisPaintInformation::KisPaintInformation(const QPointF & pos_,
qreal pressure_,
qreal xTilt_, qreal yTilt_,
qreal rotation_,
qreal tangentialPressure_,
qreal perspective_,
int time)
: d(new Private)
{
d->pos = pos_;
d->pressure = pressure_;
d->xTilt = xTilt_;
d->yTilt = yTilt_;
d->movement = movement_;
d->rotation = rotation_;
d->tangentialPressure = tangentialPressure_;
d->perspective = perspective_;
d->angle = atan2(movement_.y(), movement_.x());
d->time = time;
}
......@@ -77,8 +90,6 @@ void KisPaintInformation::toXML(QDomDocument&, QDomElement& e) const
e.setAttribute("pressure", QString::number(pressure(), 'g', 15));
e.setAttribute("xTilt", QString::number(xTilt(), 'g', 15));
e.setAttribute("yTilt", QString::number(yTilt(), 'g', 15));
e.setAttribute("movementX", QString::number(movement().x(), 'g', 15));
e.setAttribute("movementY", QString::number(movement().y(), 'g', 15));
e.setAttribute("rotation", QString::number(rotation(), 'g', 15));
e.setAttribute("tangentialPressure", QString::number(tangentialPressure(), 'g', 15));
e.setAttribute("perspective", QString::number(perspective(), 'g', 15));
......@@ -95,14 +106,21 @@ KisPaintInformation KisPaintInformation::fromXML(const QDomElement& e)
qreal perspective = qreal(e.attribute("perspective", "0.0").toDouble());
qreal xTilt = qreal(e.attribute("xTilt", "0.0").toDouble());
qreal yTilt = qreal(e.attribute("yTilt", "0.0").toDouble());
qreal movementX = qreal(e.attribute("movementX", "0.0").toDouble());
qreal movementY = qreal(e.attribute("movementY", "0.0").toDouble());
int time = e.attribute("time", "0").toInt();
return KisPaintInformation(QPointF(pointX, pointY), pressure, xTilt, yTilt, KisVector2D(movementX, movementY),
return KisPaintInformation(QPointF(pointX, pointY), pressure, xTilt, yTilt,
rotation, tangentialPressure, perspective, time);
}
void KisPaintInformation::paintAt(KisPaintOp *op, KisDistanceInformation *distanceInfo)
{
d->registerDistanceInfo(distanceInfo);
KisSpacingInformation spacingInfo = op->paintAt(*this);
d->unregisterDistanceInfo();
distanceInfo->registerPaintedDab(*this, spacingInfo);
}
const QPointF& KisPaintInformation::pos() const
{
return d->pos;
......@@ -113,11 +131,6 @@ void KisPaintInformation::setPos(const QPointF& p)
d->pos = p;
}
void KisPaintInformation::setMovement(const KisVector2D& m)
{
d->movement = m;
}
qreal KisPaintInformation::pressure() const
{
return d->pressure;
......@@ -138,14 +151,37 @@ qreal KisPaintInformation::yTilt() const
return d->yTilt;
}
KisVector2D KisPaintInformation::movement() const
qreal KisPaintInformation::drawingAngle() const
{
Q_ASSERT_X(d->currentDistanceInfo, "KisPaintInformation::drawingAngle()", "Distance info should be initialized *before* the painting");
Q_ASSERT(d->currentDistanceInfo->hasLastDabInformation());
QVector2D diff(pos() - d->currentDistanceInfo->lastPosition());
return atan2(diff.y(), diff.x());
}
qreal KisPaintInformation::drawingDistance() const
{
return d->movement;
Q_ASSERT_X(d->currentDistanceInfo, "KisPaintInformation::drawingDistance()", "Distance info should be initialized *before* the painting");
Q_ASSERT(d->currentDistanceInfo->hasLastDabInformation());
QVector2D diff(pos() - d->currentDistanceInfo->lastPosition());
return diff.length();
}
qreal KisPaintInformation::angle() const
qreal KisPaintInformation::drawingSpeed() const
{
return d->angle;
Q_ASSERT_X(d->currentDistanceInfo, "KisPaintInformation::drawingSpeed()", "Distance info should be initialized *before* the painting");
Q_ASSERT(d->currentDistanceInfo->hasLastDabInformation());
int timeDiff = currentTime() - d->currentDistanceInfo->lastTime();
if (timeDiff <= 0) {
return 6.66;
}
QVector2D diff(pos() - d->currentDistanceInfo->lastPosition());
return diff.length() / timeDiff;
}
qreal KisPaintInformation::rotation() const
......@@ -177,17 +213,18 @@ QDebug operator<<(QDebug dbg, const KisPaintInformation &info)
dbg.nospace() << ", Pressure: " << info.pressure();
dbg.nospace() << ", X Tilt: " << info.xTilt();
dbg.nospace() << ", Y Tilt: " << info.yTilt();
dbg.nospace() << ", Movement: " << toQPointF(info.movement());
dbg.nospace() << ", Rotation: " << info.rotation();
dbg.nospace() << ", Tangential Pressure: " << info.tangentialPressure();
dbg.nospace() << ", Perspective: " << info.perspective();
dbg.nospace() << ", Angle: " << info.angle();
dbg.nospace() << ", Drawing Angle: " << info.drawingAngle();
dbg.nospace() << ", Drawing Speed: " << info.drawingSpeed();
dbg.nospace() << ", Drawing Distance: " << info.drawingDistance();
dbg.nospace() << ", Time: " << info.currentTime();
#endif
return dbg.space();
}
KisPaintInformation KisPaintInformation::mix(const QPointF& p, qreal t, const KisPaintInformation& pi1, const KisPaintInformation& pi2, const KisVector2D& movement)
KisPaintInformation KisPaintInformation::mix(const QPointF& p, qreal t, const KisPaintInformation& pi1, const KisPaintInformation& pi2)
{
qreal pressure = (1 - t) * pi1.pressure() + t * pi2.pressure();
qreal xTilt = (1 - t) * pi1.xTilt() + t * pi2.xTilt();
......@@ -196,7 +233,7 @@ 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();
int time = (1 - t) * pi1.currentTime() + t * pi2.currentTime();
return KisPaintInformation(p, pressure, xTilt, yTilt, movement, rotation, tangentialPressure, perspective, time);
return KisPaintInformation(p, pressure, xTilt, yTilt, rotation, tangentialPressure, perspective, time);
}
qreal KisPaintInformation::ascension(const KisPaintInformation& info, bool normalize)
......
......@@ -28,6 +28,8 @@
class QDomDocument;
class QDomElement;
class KisPaintOp;
class KisDistanceInformation;
/**
* KisPaintInformation contains information about the input event that
......@@ -65,7 +67,6 @@ public:
qreal pressure = PRESSURE_DEFAULT,
qreal xTilt = 0.0,
qreal yTilt = 0.0,
const KisVector2D& movement = nullKisVector2D(),
qreal rotation = 0.0,
qreal tangentialPressure = 0.0,
qreal perspective = 1.0,
......@@ -77,10 +78,10 @@ public:
~KisPaintInformation();
const QPointF& pos() const;
void paintAt(KisPaintOp *op, KisDistanceInformation *distanceInfo);
const QPointF& pos() const;
void setPos(const QPointF& p);
void setMovement(const KisVector2D& m);
/// The pressure of the value (from 0.0 to 1.0)
qreal pressure() const;
......@@ -94,11 +95,29 @@ public:
/// The tilt of the pen on the vertical axis (from 0.0 to 1.0)
qreal yTilt() const;
/// The movement of the pen is equal to current position minus the last position of the call to paintAt
KisVector2D movement() const;
/**
* Current brush direction computed from the cursor movement
*
* WARNING: this method is available *only* inside paintAt() call,
* that is when the distance information is registered.
*/
qreal drawingAngle() const;
/// Rotation computed from the movement
qreal angle() const;
/**
* Current brush speed computed from the cursor movement
*
* WARNING: this method is available *only* inside paintAt() call,
* that is when the distance information is registered.
*/
qreal drawingSpeed() const;
/**
* Current distance from the previous dab
*
* WARNING: this method is available *only* inside paintAt() call,
* that is when the distance information is registered.
*/
qreal drawingDistance() const;
/// rotation as given by the tablet event
qreal rotation() const;
......@@ -117,7 +136,7 @@ public:
static KisPaintInformation fromXML(const QDomElement&);
/// (1-t) * p1 + t * p2
static KisPaintInformation mix(const QPointF& p, qreal t, const KisPaintInformation& p1, const KisPaintInformation& p2, const KisVector2D& movement);
static KisPaintInformation mix(const QPointF& p, qreal t, const KisPaintInformation& p1, const KisPaintInformation& p2);
static qreal ascension(const KisPaintInformation& info, bool normalize=true);
static qreal declination(const KisPaintInformation& info, qreal maxTiltX=60.0, qreal maxTiltY=60.0, bool normalize=true);
......
......@@ -98,14 +98,13 @@ void KisPaintOp::splitCoordinate(qreal coordinate, qint32 *whole, qreal *fractio
*fraction = f;
}
static KisDistanceInformation paintBezierCurve(KisPaintOp *paintOp,
const KisPaintInformation &pi1,
const KisVector2D &control1,
const KisVector2D &control2,
const KisPaintInformation &pi2,
const KisDistanceInformation& savedDist)
static void paintBezierCurve(KisPaintOp *paintOp,
const KisPaintInformation &pi1,
const KisVector2D &control1,
const KisVector2D &control2,
const KisPaintInformation &pi2,
KisDistanceInformation *currentDistance)
{
KisDistanceInformation newDistance;
LineEquation line = LineEquation::Through(toKisVector2D(pi1.pos()), toKisVector2D(pi2.pos()));
qreal d1 = line.absDistance(control1);
qreal d2 = line.absDistance(control2);
......@@ -116,7 +115,7 @@ static KisDistanceInformation paintBezierCurve(KisPaintOp *paintOp,
#else
|| std::isnan(d1) || std::isnan(d2)) {
#endif
newDistance = paintOp->paintLine(pi1, pi2, savedDist);
paintOp->paintLine(pi1, pi2, currentDistance);
} else {
// Midpoint subdivision. See Foley & Van Dam Computer Graphics P.508
KisVector2D l2 = (toKisVector2D(pi1.pos()) + control1) / 2;
......@@ -126,50 +125,51 @@ static KisDistanceInformation paintBezierCurve(KisPaintOp *paintOp,
KisVector2D r2 = (h + r3) / 2;
KisVector2D l4 = (l3 + r2) / 2;
KisVector2D midMovement = (pi1.movement() + pi2.movement()) * 0.5;
KisPaintInformation middlePI = KisPaintInformation::mix(toQPointF(l4), 0.5, pi1, pi2, midMovement);
KisPaintInformation middlePI = KisPaintInformation::mix(toQPointF(l4), 0.5, pi1, pi2);
newDistance = paintBezierCurve(paintOp, pi1, l2, l3, middlePI, savedDist);
newDistance = paintBezierCurve(paintOp, middlePI, r2, r3, pi2, newDistance);
paintBezierCurve(paintOp, pi1, l2, l3, middlePI, currentDistance);
paintBezierCurve(paintOp, middlePI, r2, r3, pi2, currentDistance);
}
return newDistance;
}
KisDistanceInformation KisPaintOp::paintBezierCurve(const KisPaintInformation &pi1,
const QPointF &control1,
const QPointF &control2,
const KisPaintInformation &pi2,
const KisDistanceInformation& savedDist)
void KisPaintOp::paintBezierCurve(const KisPaintInformation &pi1,
const QPointF &control1,
const QPointF &control2,
const KisPaintInformation &pi2,
KisDistanceInformation *currentDistance)
{
return ::paintBezierCurve(this, pi1, toKisVector2D(control1), toKisVector2D(control2), pi2, savedDist);
return ::paintBezierCurve(this, pi1, toKisVector2D(control1), toKisVector2D(control2), pi2, currentDistance);
}
KisDistanceInformation KisPaintOp::paintLine(const KisPaintInformation &pi1,
const KisPaintInformation &pi2,
const KisDistanceInformation& savedDist)
void KisPaintOp::paintLine(const KisPaintInformation &pi1,
const KisPaintInformation &pi2,
KisDistanceInformation *currentDistance)
{
KisDistanceInformation currentDistance = savedDist;
QPointF start = pi1.pos();
QPointF end = pi2.pos();
QVector2D dragVecNorm = QVector2D(end - start).normalized();
KisPaintInformation pi = pi1;
QPointF pt = start;
QPointF pt = pi1.pos();
qreal t = 0.0;
while ((t = currentDistance.getNextPointPosition(pt, end)) >= 0.0) {
qreal spacingApproximation = currentDistance.spacing().scalarApprox();
KisVector2D movement = toKisVector2D((dragVecNorm * spacingApproximation).toPointF());
while ((t = currentDistance->getNextPointPosition(pt, end)) >= 0.0) {
pt = pt + t * (end - pt);
pi = KisPaintInformation::mix(pt, t, pi, pi2, movement);
pi = KisPaintInformation::mix(pt, t, pi, pi2);
currentDistance.setSpacing(paintAt(pi));
/**
* A bit complicated part to ensure the registration
* of the distance information is done in right order
*/
pi.paintAt(this, currentDistance);
}
}
void KisPaintOp::paintAt(const KisPaintInformation& info, KisDistanceInformation *currentDistance)
{
Q_ASSERT(currentDistance);
return currentDistance;
KisPaintInformation pi(info);
pi.paintAt(this, currentDistance);
}
KisPainter* KisPaintOp::painter() const
......
......@@ -54,10 +54,11 @@ public:
* Paint at the subpixel point pos using the specified paint
* information..
*
* The distance between two calls of the paintAt is always specified by spacing;
* xSpacing and ySpacing is 1.0 by default, negative values causes infinite loops (it is checked by Q_ASSERT)
* The distance between two calls of the paintAt is always
* specified by spacing, which is automatically saved into the
* current distance information object
*/
virtual KisSpacingInformation paintAt(const KisPaintInformation& info) = 0;
void paintAt(const KisPaintInformation& info, KisDistanceInformation *currentDistance);
/**
* Draw a line between pos1 and pos2 using the currently set brush and color.
......@@ -68,9 +69,9 @@ public:
* between p1 and p2 not covered because the currenlty set brush
* has a spacing greater than that distance.
*/
virtual KisDistanceInformation paintLine(const KisPaintInformation &pi1,
const KisPaintInformation &pi2,
const KisDistanceInformation& savedDist = KisDistanceInformation());
virtual void paintLine(const KisPaintInformation &pi1,
const KisPaintInformation &pi2,
KisDistanceInformation *currentDistance);
/**
* Draw a Bezier curve between pos1 and pos2 using control points 1 and 2.
......@@ -79,11 +80,11 @@ public:
* @return the drag distance, that is the remains of the distance between p1 and p2 not covered
* because the currenlty set brush has a spacing greater than that distance.
*/
virtual KisDistanceInformation paintBezierCurve(const KisPaintInformation &pi1,
const QPointF &control1,
const QPointF &control2,
const KisPaintInformation &pi2,
const KisDistanceInformation& savedDist = KisDistanceInformation());
virtual void paintBezierCurve(const KisPaintInformation &pi1,
const QPointF &control1,
const QPointF &control2,
const KisPaintInformation &pi2,
KisDistanceInformation *currentDistance);
/**
......@@ -108,6 +109,12 @@ public:
qreal currentRotation() const;
protected:
friend class KisPaintInformation;
/**
* The implementation of painting of a dab
*/
virtual KisSpacingInformation paintAt(const KisPaintInformation& info) = 0;
void setCurrentScale(qreal scale);
/**
......
......@@ -18,21 +18,20 @@
*/
#include "kis_distance_information.h"
#include "kis_paint_information.h"
#include "kis_debug.h"
#include <QtCore/qmath.h>
#include <QVector2D>
inline qreal pow2(qreal x) {
return x * x;
}
template<>
inline QPointF qAbs(const QPointF &pt) {
return QPointF(qAbs(pt.x()), qAbs(pt.y()));
}
struct KisDistanceInformation::Private {
Private() : lastDabInfoValid(false) {}
QPointF distance;
KisSpacingInformation spacing;
QPointF lastPosition;
int lastTime;
bool lastDabInfoValid;
};
KisDistanceInformation::KisDistanceInformation()
......@@ -40,6 +39,16 @@ KisDistanceInformation::KisDistanceInformation()
{
}
KisDistanceInformation::KisDistanceInformation(const QPointF &lastPosition,
int lastTime)
: m_d(new Private)
{
m_d->lastPosition = lastPosition;
m_d->lastTime = lastTime;
m_d->lastDabInfoValid = true;
}
KisDistanceInformation::KisDistanceInformation(const KisDistanceInformation &rhs)
: m_d(new Private(*rhs.m_d))
{
......@@ -57,13 +66,33 @@ KisDistanceInformation::~KisDistanceInformation()
delete m_d;
}
const KisSpacingInformation& KisDistanceInformation::spacing() const
const KisSpacingInformation& KisDistanceInformation::currentSpacing() const
{
return m_d->spacing;
}
void KisDistanceInformation::setSpacing(const KisSpacingInformation &spacing)
bool KisDistanceInformation::hasLastDabInformation() const
{