From fb6bc855e9f9c9e5275abc20b0e93e7515ac6b8c Mon Sep 17 00:00:00 2001 From: Craig Watson Date: Wed, 23 Mar 2016 09:30:41 +0100 Subject: [PATCH 1/9] Remove polygonItems from their Stroke when added to a StrokesGroup --- src/domain/UBGraphicsPolygonItem.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/domain/UBGraphicsPolygonItem.cpp b/src/domain/UBGraphicsPolygonItem.cpp index 3ba4861c..4effa158 100644 --- a/src/domain/UBGraphicsPolygonItem.cpp +++ b/src/domain/UBGraphicsPolygonItem.cpp @@ -114,6 +114,9 @@ UBGraphicsPolygonItem::~UBGraphicsPolygonItem() void UBGraphicsPolygonItem::setStrokesGroup(UBGraphicsStrokesGroup *group) { + if (mStroke) + mStroke->remove(this); + mpGroup = group; } From 4491341a4d324297e26ed675092b31108e6faf3f Mon Sep 17 00:00:00 2001 From: Craig Watson Date: Fri, 1 Apr 2016 12:41:25 +0200 Subject: [PATCH 2/9] (WIP) Interpolation of strokes. Progress so far: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - UBGraphicsScene calls UBGraphicsStroke::addPoint, which returns a list of points that can be drawn. It may be none (e.g we discard very small segments), one (if we do no interpolation) or several. - Added a UBInterpolator base, abstract class. Various interpolation methods can be added easily. - Current methods: Basic spline (custom), Catmull-Rom spline (based on alglib), and Bézier - Added a setting to toggle interpolation. Added this to the UI as well --- OpenBoard.pro | 3 + resources/forms/brushProperties.ui | 415 ++++++++++++++------------- src/core/UBPreferencesController.cpp | 2 + src/core/UBSettings.cpp | 2 + src/core/UBSettings.h | 2 + src/domain/UBGraphicsScene.cpp | 88 +++++- src/domain/UBGraphicsScene.h | 1 + src/domain/UBGraphicsStroke.cpp | 171 +++++++++++ src/domain/UBGraphicsStroke.h | 11 + src/frameworks/UBInterpolator.cpp | 139 +++++++++ src/frameworks/UBInterpolator.h | 96 +++++++ src/frameworks/frameworks.pri | 6 +- 12 files changed, 732 insertions(+), 204 deletions(-) create mode 100644 src/frameworks/UBInterpolator.cpp create mode 100644 src/frameworks/UBInterpolator.h diff --git a/OpenBoard.pro b/OpenBoard.pro index 4558b666..13b005e6 100644 --- a/OpenBoard.pro +++ b/OpenBoard.pro @@ -66,6 +66,9 @@ INCLUDEPATH += src/pdf-merger include(src/pdf-merger/pdfMerger.pri) #ThirdParty +INCLUDEPATH += $$THIRD_PARTY_PATH/spline/ +INCLUDEPATH += $$THIRD_PARTY_PATH/alglib/ +include($$THIRD_PARTY_PATH/alglib/alglib.pri) DEPENDPATH += $$THIRD_PARTY_PATH/quazip/ INCLUDEPATH += $$THIRD_PARTY_PATH/quazip/ include($$THIRD_PARTY_PATH/quazip/quazip.pri) diff --git a/resources/forms/brushProperties.ui b/resources/forms/brushProperties.ui index 19883e90..eea50fba 100644 --- a/resources/forms/brushProperties.ui +++ b/resources/forms/brushProperties.ui @@ -1,7 +1,8 @@ - + + brushProperties - - + + 0 0 @@ -9,25 +10,25 @@ 808 - + - + QFrame::NoFrame - + QFrame::Plain - - - - + + + + Qt::Vertical - + QSizePolicy::Fixed - + 20 40 @@ -35,53 +36,40 @@ - - - - Qt::Horizontal - - - - 0 - 0 - - - - - - - + + + QFrame::NoFrame - + QFrame::Raised - - + + 0 - - + + QFrame::NoFrame - + QFrame::Raised - + - - + + On Light Background - - + + Qt::Horizontal - + 40 20 @@ -90,17 +78,17 @@ - - + + 32 32 - + QFrame::StyledPanel - + QFrame::Raised @@ -109,27 +97,27 @@ - - + + QFrame::NoFrame - + QFrame::Raised - + - - + + On Dark Background - - + + Qt::Horizontal - + 40 20 @@ -138,17 +126,17 @@ - - + + 32 32 - + QFrame::StyledPanel - + QFrame::Raised @@ -159,34 +147,21 @@ - - - - Qt::Horizontal - - - - 0 - 0 - - - - - - - + + + QFrame::NoFrame - + QFrame::Raised - + - - + + Qt::Horizontal - + 154 20 @@ -195,40 +170,40 @@ - - + + Opacity - - + + 20 - + 100 - + 50 - + Qt::Horizontal - + QSlider::TicksAbove - + 20 - - + + Qt::Horizontal - + 156 20 @@ -239,40 +214,27 @@ - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - + + + + 0 0 - + Line Width - - - - + + + + Qt::Horizontal - + QSizePolicy::Fixed - + 40 20 @@ -280,15 +242,15 @@ - - - + + + Qt::Horizontal - + QSizePolicy::Fixed - + 40 20 @@ -296,149 +258,149 @@ - - - + + + 5 - + 500 - + 5 - + Qt::Horizontal - + QSlider::TicksAbove - + 100 - - - + + + Medium - - - + + + 5 - + 500 - + Qt::Horizontal - + QSlider::TicksAbove - + 100 - - - + + + Strong - - - + + + 5 - + 500 - + Qt::Horizontal - + QSlider::TicksAbove - + 100 - - - - + + + + 60 60 - + 60 60 - + QFrame::StyledPanel - + QFrame::Raised - - - - + + + + 60 60 - + 60 60 - + QFrame::StyledPanel - + QFrame::Raised - - - - + + + + 60 60 - + 60 60 - + QFrame::StyledPanel - + QFrame::Raised - - - + + + Fine @@ -446,50 +408,85 @@ - - - + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Qt::Horizontal + + + + 0 + 0 + + + + + + + + Qt::Horizontal + + + + 0 + 0 + + + + + + + QFrame::NoFrame - + QFrame::Raised - - + + + 0 + + + 0 + + + 0 + + 0 - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - + + + 0 0 - + Pen is Pressure Sensitive - - + + Qt::Horizontal - + 198 20 @@ -500,6 +497,30 @@ + + + + + + Smooth strokes (experimental) + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + diff --git a/src/core/UBPreferencesController.cpp b/src/core/UBPreferencesController.cpp index e2ec529e..56baf2b4 100644 --- a/src/core/UBPreferencesController.cpp +++ b/src/core/UBPreferencesController.cpp @@ -163,6 +163,7 @@ void UBPreferencesController::wire() connect(mPenProperties->mediumSlider, SIGNAL(valueChanged(int)), this, SLOT(widthSliderChanged(int))); connect(mPenProperties->strongSlider, SIGNAL(valueChanged(int)), this, SLOT(widthSliderChanged(int))); connect(mPenProperties->pressureSensitiveCheckBox, SIGNAL(clicked(bool)), settings, SLOT(setPenPressureSensitive(bool))); + connect(mPenProperties->interpolateStrokesCheckBox, SIGNAL(clicked(bool)), settings->boardInterpolatePenStrokes, SLOT(setBool(bool))); // marker QList markerLightBackgroundColors = settings->boardMarkerLightBackgroundColors->colors(); @@ -229,6 +230,7 @@ void UBPreferencesController::init() mPenProperties->mediumSlider->setValue(settings->boardPenMediumWidth->get().toDouble() * sSliderRatio); mPenProperties->strongSlider->setValue(settings->boardPenStrongWidth->get().toDouble() * sSliderRatio); mPenProperties->pressureSensitiveCheckBox->setChecked(settings->boardPenPressureSensitive->get().toBool()); + mPenProperties->interpolateStrokesCheckBox->setChecked(settings->boardInterpolatePenStrokes->get().toBool()); // marker tab mMarkerProperties->fineSlider->setValue(settings->boardMarkerFineWidth->get().toDouble() * sSliderRatio); diff --git a/src/core/UBSettings.cpp b/src/core/UBSettings.cpp index d0dfc237..9a32b48e 100644 --- a/src/core/UBSettings.cpp +++ b/src/core/UBSettings.cpp @@ -267,6 +267,8 @@ void UBSettings::init() boardUseHighResTabletEvent = new UBSetting(this, "Board", "UseHighResTabletEvent", true); + boardInterpolatePenStrokes = new UBSetting(this, "Board", "InterpolatePenStrokes", true); + boardKeyboardPaletteKeyBtnSize = new UBSetting(this, "Board", "KeyboardPaletteKeyBtnSize", "16x16"); ValidateKeyboardPaletteKeyBtnSize(); diff --git a/src/core/UBSettings.h b/src/core/UBSettings.h index b7e0bc08..f4210f19 100644 --- a/src/core/UBSettings.h +++ b/src/core/UBSettings.h @@ -269,6 +269,8 @@ class UBSettings : public QObject UBSetting* boardUseHighResTabletEvent; + UBSetting* boardInterpolatePenStrokes; + UBSetting* boardKeyboardPaletteKeyBtnSize; UBSetting* appStartMode; diff --git a/src/domain/UBGraphicsScene.cpp b/src/domain/UBGraphicsScene.cpp index f4091c21..72eae6a9 100644 --- a/src/domain/UBGraphicsScene.cpp +++ b/src/domain/UBGraphicsScene.cpp @@ -23,6 +23,7 @@ */ +#include #include "UBGraphicsScene.h" @@ -35,6 +36,7 @@ #include "frameworks/UBGeometryUtils.h" #include "frameworks/UBPlatformUtils.h" +#include "frameworks/UBInterpolator.h" #include "core/UBApplication.h" #include "core/UBSettings.h" @@ -430,6 +432,8 @@ bool UBGraphicsScene::inputDevicePress(const QPointF& scenePos, const qreal& pre else { moveTo(scenePos); drawLineTo(scenePos, width, UBDrawingController::drawingController()->stylusTool() == UBStylusTool::Line); + + mCurrentStroke->addPoint(scenePos); } accepted = true; } @@ -537,7 +541,48 @@ bool UBGraphicsScene::inputDeviceMove(const QPointF& scenePos, const qreal& pres dc->mActiveRuler->DrawLine(position, width); } else{ - drawLineTo(position, width, UBDrawingController::drawingController()->stylusTool() == UBStylusTool::Line); + bool showDots = false; + + if (showDots) { + UBGraphicsPolygonItem * p = lineToPolygonItem(QLineF(scenePos, scenePos), width, width); + p->setColor(Qt::red); + this->addItem(p); + } + + UBInterpolator::InterpolationMethod interpolator = UBInterpolator::NoInterpolation; + + if (currentTool == UBStylusTool::Marker) { + // The marker is already super slow due to the transparency, we can't also do interpolation + interpolator = UBInterpolator::NoInterpolation; + } + + else if (UBSettings::settings()->boardInterpolatePenStrokes->get().toBool()) { + interpolator = UBInterpolator::Bezier; + } + + QList newPoints = mCurrentStroke->addPoint(scenePos, interpolator); + int n = newPoints.length(); + int i = 1; + qreal startWidth = mPreviousWidth; + foreach(QPointF point, newPoints) { + + if (showDots) { + if (point != scenePos) { + UBGraphicsPolygonItem * p = lineToPolygonItem(QLineF(point, point), width, width); + p->setColor(Qt::yellow); + this->addItem(p); + } + } + + + // linear interpolation of the end width + qreal endWidth = mPreviousWidth + qreal(i)*(width - mPreviousWidth)/qreal(n); + i++; + + drawLineTo(point, startWidth, endWidth, UBDrawingController::drawingController()->stylusTool() == UBStylusTool::Line); + startWidth = endWidth; + } + //drawLineTo(scenePos, width, UBDrawingController::drawingController()->stylusTool() == UBStylusTool::Line); } } else if (currentTool == UBStylusTool::Eraser) @@ -606,6 +651,30 @@ bool UBGraphicsScene::inputDeviceRelease() mDrawWithCompass = false; } else if (mCurrentStroke){ + /* + // ------------------------------------------------------------------------- + // Replace the stroke by a smoother one + // ------------------------------------------------------------------------- + UBGraphicsStroke* smoothStroke = mCurrentStroke->smoothe(); + + foreach(UBGraphicsPolygonItem* poly, mCurrentStroke->polygons()){ + mPreviousPolygonItems.removeAll(poly); + removeItem(poly); + } + delete mCurrentStroke; + mCurrentStroke = smoothStroke; + + moveTo(smoothStroke->points()[0]); + for (int i(1); i < smoothStroke->points().size(); ++i) { + QPointF currentPoint = smoothStroke->points()[i]; + + drawLineTo(currentPoint, dc->currentToolWidth(), dc->currentToolWidth(), false); + moveTo(currentPoint); + } + + // ------------------------------------------------------------------------- + */ + UBGraphicsStrokesGroup* pStrokes = new UBGraphicsStrokesGroup(); // Remove the strokes that were just drawn here and replace them by a stroke item @@ -771,15 +840,24 @@ void UBGraphicsScene::moveTo(const QPointF &pPoint) mArcPolygonItem = 0; mDrawWithCompass = false; } - void UBGraphicsScene::drawLineTo(const QPointF &pEndPoint, const qreal &pWidth, bool bLineStyle) +{ + drawLineTo(pEndPoint, pWidth, pWidth, bLineStyle); + +} + +void UBGraphicsScene::drawLineTo(const QPointF &pEndPoint, const qreal &startWidth, const qreal &endWidth, bool bLineStyle) { if (mPreviousWidth == -1.0) - mPreviousWidth = pWidth; + mPreviousWidth = startWidth; + + qreal initialWidth = startWidth; + if (initialWidth == endWidth) + initialWidth = mPreviousWidth; // UBGraphicsPolygonItem *polygonItem = lineToPolygonItem(QLineF(mPreviousPoint, pEndPoint), pWidth); - UBGraphicsPolygonItem *polygonItem = lineToPolygonItem(QLineF(mPreviousPoint, pEndPoint), mPreviousWidth,pWidth); + UBGraphicsPolygonItem *polygonItem = lineToPolygonItem(QLineF(mPreviousPoint, pEndPoint), initialWidth, endWidth); if (!polygonItem->brush().isOpaque()) { @@ -822,7 +900,7 @@ void UBGraphicsScene::drawLineTo(const QPointF &pEndPoint, const qreal &pWidth, if (!bLineStyle) { mPreviousPoint = pEndPoint; - mPreviousWidth = pWidth; + mPreviousWidth = endWidth; } } diff --git a/src/domain/UBGraphicsScene.h b/src/domain/UBGraphicsScene.h index b040cf2e..19571d62 100644 --- a/src/domain/UBGraphicsScene.h +++ b/src/domain/UBGraphicsScene.h @@ -191,6 +191,7 @@ class UBGraphicsScene: public UBCoreGraphicsScene, public UBItem void moveTo(const QPointF& pPoint); void drawLineTo(const QPointF& pEndPoint, const qreal& pWidth, bool bLineStyle); + void drawLineTo(const QPointF& pEndPoint, const qreal& pStartWidth, const qreal& endWidth, bool bLineStyle); void eraseLineTo(const QPointF& pEndPoint, const qreal& pWidth); void drawArcTo(const QPointF& pCenterPoint, qreal pSpanAngle); diff --git a/src/domain/UBGraphicsStroke.cpp b/src/domain/UBGraphicsStroke.cpp index f6954b47..9208315c 100644 --- a/src/domain/UBGraphicsStroke.cpp +++ b/src/domain/UBGraphicsStroke.cpp @@ -31,6 +31,7 @@ #include "core/memcheck.h" + UBGraphicsStroke::UBGraphicsStroke() { // NOOP @@ -61,6 +62,129 @@ QList UBGraphicsStroke::polygons() const return mPolygons; } +/** + * @brief Add a point to the curve, interpolating extra points if required + * @return The points (or point, if none were interpolated) that were added + */ +QList UBGraphicsStroke::addPoint(const QPointF& point, UBInterpolator::InterpolationMethod interpolationMethod) +{ + int n = mDrawnPoints.size(); + + /* + if (n > 0) { + qreal MIN_DISTANCE = 3; + QPointF lastPoint = mDrawnPoints.last(); + qreal distance = QLineF(lastPoint, point).length(); + + //qDebug() << "distance: " << distance; + + if (distance < MIN_DISTANCE) + return QList(); + } + */ + + if (interpolationMethod == UBInterpolator::NoInterpolation || n == 0) { + mDrawnPoints << point; + mAllPoints << point; + return QList() << point; + } + + else if (interpolationMethod == UBInterpolator::Bezier) { + // This is a bit special, as the curve we are interpolating is not between two drawn points; + // it is between the midway points of the second-to-last and last point, and last and current point. + qreal MIN_DISTANCE = 3; + QPointF lastPoint = mDrawnPoints.last(); + qreal distance = QLineF(lastPoint, point).length(); + + //qDebug() << "distance: " << distance; + + if (distance < MIN_DISTANCE) + return QList(); + if (n == 1) { + // We start with a straight line to the first midway point + QPointF lastPoint = mDrawnPoints[0]; + mDrawnPoints << point; + + return QList() << ((lastPoint + point)/2.0); + } + + QPointF p0 = mDrawnPoints[mDrawnPoints.size() - 2]; + QPointF p1 = mDrawnPoints[mDrawnPoints.size() - 1]; + QPointF p2 = point; + + UBQuadraticBezier bz; + + QPointF startPoint = (p1+p0)/2.0; + QPointF endPoint = (p2+p1)/2.0; + + bz.setPoints(startPoint, p1, endPoint); + + QList newPoints = bz.getPoints(7); + + foreach(QPointF p, newPoints) { + mAllPoints << p; + } + + mDrawnPoints << point; + return newPoints; + + + } + + else { + + qreal MIN_INTERPOLATION_DISTANCE = 0; + + QPointF lastPoint; + if (!mDrawnPoints.isEmpty()) + lastPoint = mDrawnPoints.last(); + + QList newPoints; + + // Interpolation + if (n > 3 && QLineF(lastPoint, point).length() > MIN_INTERPOLATION_DISTANCE) { + + /* + UBSimpleSpline sp; + sp.setPoints(mDrawnPoints[n-2], mDrawnPoints[n-1], point); + */ + + // todo: better way of avoiding problems with constant x's. in the meantime this'll do. + qreal x0 = mDrawnPoints[n-3].x(); + qreal x1 = mDrawnPoints[n-2].x(); + qreal x2 = mDrawnPoints[n-1].x(); + qreal x3 = point.x(); + if (!(x0 == x1 || x0 == x2 || x0 == x3 || x1 == x2 || x1 == x3 || x2 == x3)) { + + UBCatmullRomSpline sp; + sp.setPoints(QList() << mDrawnPoints[n-3] << mDrawnPoints[n-2] << mDrawnPoints[n-1] << point); + + // get an extra 3 values in between the current point and last one + int n_points = 3; // number of points to interpolate + double interval = (point.x() - lastPoint.x())/double(n_points+1); + + qDebug() << "Interpolating between: " << lastPoint << " and " << point; + + for (int i(1); i <= n_points; ++i) { + double x = lastPoint.x() + i*interval; + QPointF newPoint(x, sp.y(x)); + qDebug() << newPoint; + + newPoints << newPoint; + mAllPoints << newPoint; + //qDebug() << "Got new point: " << newPoint; + } + } + + } + + newPoints << point; + mAllPoints << point; + mDrawnPoints << point; + + return newPoints; + } +} bool UBGraphicsStroke::hasPressure() { @@ -105,3 +229,50 @@ void UBGraphicsStroke::clear() } } + +/** + * @brief Smoothe the curve, by interpolating extra points where needed. + * + * @return A new stroke based on the current one. + */ +UBGraphicsStroke* UBGraphicsStroke::smoothe() +{ + // Catmull-Rom spline interpolation + UBCatmullRomSpline sp; + + UBGraphicsStroke * smoothStroke = new UBGraphicsStroke(); + + smoothStroke->mAllPoints << mAllPoints[0]; + + for (int i(0); i < mAllPoints.size() - 3; ++i) { + QPointF p1, p2; + + p1 = mAllPoints[i+1]; + p2 = mAllPoints[i+2]; + + qreal x0 = mAllPoints[i].x(); + qreal x1 = p1.x(); + qreal x2 = p2.x(); + qreal x3 = mAllPoints[i+3].x(); + + if (!(x0 == x1 || x0 == x2 || x0 == x3 || x1 == x2 || x1 == x3 || x2 == x3)) { + sp.setPoints(QList() << mAllPoints[i] << mAllPoints[i+1] << mAllPoints[i+2] << mAllPoints[i+3]); + + smoothStroke->mAllPoints << mAllPoints[i+1]; + int n_points = 3; // number of points to interpolate + double interval = (p2.x() - p1.x())/double(n_points+1); + + for (int i(1); i <= n_points; ++i) { + double x = p1.x() + i*interval; + QPointF newPoint(x, sp.y(x)); + + smoothStroke->mAllPoints << newPoint; + } + } + + } + smoothStroke->mAllPoints << mAllPoints[mAllPoints.size() - 2] << mAllPoints[mAllPoints.size() - 1]; + + + return smoothStroke; +} diff --git a/src/domain/UBGraphicsStroke.h b/src/domain/UBGraphicsStroke.h index d7f8852d..2f9b2759 100644 --- a/src/domain/UBGraphicsStroke.h +++ b/src/domain/UBGraphicsStroke.h @@ -31,6 +31,8 @@ #include #include "core/UB.h" +#include "frameworks/UBInterpolator.h" + class UBGraphicsPolygonItem; @@ -55,6 +57,12 @@ class UBGraphicsStroke void clear(); + QList addPoint(const QPointF& point, UBInterpolator::InterpolationMethod interpolationMethod = UBInterpolator::NoInterpolation); + + UBGraphicsStroke* smoothe(); + + const QList& points() { return mAllPoints; } + protected: void addPolygon(UBGraphicsPolygonItem* pol); @@ -62,6 +70,9 @@ class UBGraphicsStroke QList mPolygons; + QList mDrawnPoints; + QList mAllPoints; + }; #endif /* UBGRAPHICSSTROKE_H_ */ diff --git a/src/frameworks/UBInterpolator.cpp b/src/frameworks/UBInterpolator.cpp new file mode 100644 index 00000000..bd40b038 --- /dev/null +++ b/src/frameworks/UBInterpolator.cpp @@ -0,0 +1,139 @@ +#include "UBInterpolator.h" + +UBInterpolator::UBInterpolator() +{ +} + +UBInterpolator::~UBInterpolator() +{ +} + + +UBSimpleSpline::UBSimpleSpline() +{ + +} + +void UBSimpleSpline::setPoints(QList points) +{ + setPoints(points[0], points[1], points[2]); +} + +void UBSimpleSpline::setPoints(QPointF p0, QPointF p1, QPointF p2) +{ + /* + p0 -= p0; + p1 -= p0; + p2 -= p0; + */ + long double x0, x1, x2, y0, y1, y2; + x0 = p0.x(); + x1 = p1.x(); + x2 = p2.x(); + y0 = p0.y(); + y1 = p1.y(); + y2 = p2.y(); + + long double k1 = (y2-y0)/(x2-x0); + + m_a = (y1-y2-k1*(x1-x2))/(pow(x1,3) - pow(x2,3) - 3*x2*(pow(x1,2)-pow(x2,2)) - + 3*(pow(x1,2) - 2*x1*x2)*(x1-x2)); + + m_b = -3*m_a*x2; + m_c = k1 - 3*m_a*pow(x1,2) - 2*m_b*x1; + m_d = y1 - m_a*pow(x1,3) - m_b*pow(x1,2) - m_c*x1; +} + +double UBSimpleSpline::y(double x) +{ + return m_a*pow(x, 3) + m_b*pow(x, 2) + m_c*x + m_d; +} + +UBCatmullRomSpline::UBCatmullRomSpline() +{ + mInterpolant = 0; +} + +UBCatmullRomSpline::~UBCatmullRomSpline() +{ + if (mInterpolant) + delete mInterpolant; +} + +void UBCatmullRomSpline::setPoints(QList points) +{ + // todo : basis change to avoid crashing when several X's are equal + mInterpolant = new alglib::spline1dinterpolant(); + + // alglib arrays are defined as strings + QString x = "["; + QString y = "["; + + foreach(QPointF point, points) { + x += (QString::number(point.x()) + QString(",")); + y += (QString::number(point.y()) + QString(",")); + } + + x.chop(1); + y.chop(1); + + x+="]"; + y+="]"; + + alglib::real_1d_array xArray = x.toLatin1().data(); + alglib::real_1d_array yArray = y.toLatin1().data(); + + alglib::spline1dbuildcatmullrom(xArray, yArray, *mInterpolant); + + +} + +double UBCatmullRomSpline::y(double x) +{ + return alglib::spline1dcalc(*mInterpolant, x); +} + + +UBQuadraticBezier::UBQuadraticBezier() +{ + mPath = 0; +} + +UBQuadraticBezier::~UBQuadraticBezier() +{ + if (mPath) + delete mPath; +} + +void UBQuadraticBezier::setPoints(QList points) +{ + setPoints(points[0], points[1], points[2]); + +} + +void UBQuadraticBezier::setPoints(QPointF start, QPointF control, QPointF end) +{ + mPath = new QPainterPath(start); + mPath->quadTo(control, end); +} + +/** + * @brief Return n points along the curve, including start and end points (thus n should be larger than or equal to 2). + * + * The higher n, the more accurate the result + */ +QList UBQuadraticBezier::getPoints(int n) +{ + QList points; + + if (n <= 1) + return points; + + for (int i(0); i <= n; ++i) { + qreal percent = qreal(i)/qreal(n); + + points << mPath->pointAtPercent(percent); + } + + return points; +} diff --git a/src/frameworks/UBInterpolator.h b/src/frameworks/UBInterpolator.h new file mode 100644 index 00000000..1a028523 --- /dev/null +++ b/src/frameworks/UBInterpolator.h @@ -0,0 +1,96 @@ +#ifndef UBINTERPOLATOR_H +#define UBINTERPOLATOR_H + +#include + +#include "spline.h" +#include "interpolation.h" + +class UBInterpolator +{ + /* Abstract class representing an interpolator */ + +public: + enum InterpolationMethod { + NoInterpolation, + SimpleSpline, + CatmullRom, + Bezier + }; + + UBInterpolator(); + virtual ~UBInterpolator(); + + virtual void setPoints(QList points) = 0; + virtual double y(double x) {} + +}; + + +class UBSimpleSpline : public UBInterpolator +{ + /* A basic cubic spline interpolator, that requires only three + * points to interpolate between the second and third one. + * To do so, the curvature at p2 is set to 0, so the resulting + * curve is not very smooth. + * However, it is better than linear interpolation and requires no + * "future" points, so it can be used seamlessly during drawing. + */ + +public: + UBSimpleSpline(); + virtual ~UBSimpleSpline() {} + + virtual void setPoints(QList points); + void setPoints(QPointF p0, QPointF p1, QPointF p2); + + virtual double y(double x); + +private: + long double m_a, + m_b, + m_c, + m_d; +}; + + +class UBCatmullRomSpline : public UBInterpolator +{ + /* Catmull-Rom spline, using AlgLib as backend + * + * This requires four points to interpolate between the middle two. + */ + +public: + UBCatmullRomSpline(); + virtual ~UBCatmullRomSpline(); + + virtual void setPoints(QList points); + virtual double y(double x); + +private: + alglib::spline1dinterpolant * mInterpolant; + +}; + + +class UBQuadraticBezier : public UBInterpolator +{ + +public: + UBQuadraticBezier(); + virtual ~UBQuadraticBezier(); + + virtual void setPoints(QList points); + void setPoints(QPointF start, QPointF control, QPointF end); + + //virtual double y(double x); + + QList getPoints(int n); + +private: + + QPainterPath* mPath; +}; + +#endif // UBINTERPOLATOR_H diff --git a/src/frameworks/frameworks.pri b/src/frameworks/frameworks.pri index 74d6edbb..8d6d435d 100644 --- a/src/frameworks/frameworks.pri +++ b/src/frameworks/frameworks.pri @@ -6,7 +6,8 @@ HEADERS += src/frameworks/UBGeometryUtils.h \ src/frameworks/UBVersion.h \ src/frameworks/UBCoreGraphicsScene.h \ src/frameworks/UBCryptoUtils.h \ - src/frameworks/UBBase32.h + src/frameworks/UBBase32.h \ + $$PWD/UBInterpolator.h SOURCES += src/frameworks/UBGeometryUtils.cpp \ src/frameworks/UBPlatformUtils.cpp \ @@ -15,7 +16,8 @@ SOURCES += src/frameworks/UBGeometryUtils.cpp \ src/frameworks/UBVersion.cpp \ src/frameworks/UBCoreGraphicsScene.cpp \ src/frameworks/UBCryptoUtils.cpp \ - src/frameworks/UBBase32.cpp + src/frameworks/UBBase32.cpp \ + $$PWD/UBInterpolator.cpp win32 { From 19cfe0ac1c87e8f29e8e77a7c3966779a272531a Mon Sep 17 00:00:00 2001 From: Craig Watson Date: Mon, 4 Apr 2016 10:03:47 +0200 Subject: [PATCH 3/9] Revert "Remove polygonItems from their Stroke when added to a StrokesGroup" This reverts commit 177571ca79cbd92f6544c80ce36ded88ce64fa7a. --- src/domain/UBGraphicsPolygonItem.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/domain/UBGraphicsPolygonItem.cpp b/src/domain/UBGraphicsPolygonItem.cpp index 4effa158..3ba4861c 100644 --- a/src/domain/UBGraphicsPolygonItem.cpp +++ b/src/domain/UBGraphicsPolygonItem.cpp @@ -114,9 +114,6 @@ UBGraphicsPolygonItem::~UBGraphicsPolygonItem() void UBGraphicsPolygonItem::setStrokesGroup(UBGraphicsStrokesGroup *group) { - if (mStroke) - mStroke->remove(this); - mpGroup = group; } From 4811d3501790a63d52baa39ec20893477110fb07 Mon Sep 17 00:00:00 2001 From: Craig Watson Date: Tue, 5 Apr 2016 14:49:56 +0200 Subject: [PATCH 4/9] Improved drawing of interpolated strokes: - Added a curveToPolygon function that creates a curved polygon, eliminating the need to generate lots of small ones to make a curve look smooth. - Cleaned up the rest of the code a bit --- src/domain/UBGraphicsScene.cpp | 96 +++++++++++++------------- src/domain/UBGraphicsScene.h | 3 + src/domain/UBGraphicsStroke.cpp | 29 +++----- src/domain/UBGraphicsStroke.h | 3 + src/frameworks/UBGeometryUtils.cpp | 105 +++++++++++++++++++++++++++++ src/frameworks/UBGeometryUtils.h | 1 + 6 files changed, 167 insertions(+), 70 deletions(-) diff --git a/src/domain/UBGraphicsScene.cpp b/src/domain/UBGraphicsScene.cpp index 72eae6a9..d19a9457 100644 --- a/src/domain/UBGraphicsScene.cpp +++ b/src/domain/UBGraphicsScene.cpp @@ -537,52 +537,31 @@ bool UBGraphicsScene::inputDeviceMove(const QPointF& scenePos, const qreal& pres position = newPosition; } + if (!mCurrentStroke) + mCurrentStroke = new UBGraphicsStroke(); + if(dc->mActiveRuler){ dc->mActiveRuler->DrawLine(position, width); } else{ - bool showDots = false; - - if (showDots) { - UBGraphicsPolygonItem * p = lineToPolygonItem(QLineF(scenePos, scenePos), width, width); - p->setColor(Qt::red); - this->addItem(p); - } - UBInterpolator::InterpolationMethod interpolator = UBInterpolator::NoInterpolation; + /* if (currentTool == UBStylusTool::Marker) { // The marker is already super slow due to the transparency, we can't also do interpolation interpolator = UBInterpolator::NoInterpolation; } + */ - else if (UBSettings::settings()->boardInterpolatePenStrokes->get().toBool()) { + if (UBSettings::settings()->boardInterpolatePenStrokes->get().toBool()) { interpolator = UBInterpolator::Bezier; } QList newPoints = mCurrentStroke->addPoint(scenePos, interpolator); - int n = newPoints.length(); - int i = 1; - qreal startWidth = mPreviousWidth; - foreach(QPointF point, newPoints) { - - if (showDots) { - if (point != scenePos) { - UBGraphicsPolygonItem * p = lineToPolygonItem(QLineF(point, point), width, width); - p->setColor(Qt::yellow); - this->addItem(p); - } - } - - - // linear interpolation of the end width - qreal endWidth = mPreviousWidth + qreal(i)*(width - mPreviousWidth)/qreal(n); - i++; - - drawLineTo(point, startWidth, endWidth, UBDrawingController::drawingController()->stylusTool() == UBStylusTool::Line); - startWidth = endWidth; - } - //drawLineTo(scenePos, width, UBDrawingController::drawingController()->stylusTool() == UBStylusTool::Line); + if (newPoints.length() > 1) + drawCurve(newPoints, mPreviousWidth, width); + else + drawLineTo(scenePos, width, UBDrawingController::drawingController()->stylusTool() == UBStylusTool::Line); } } else if (currentTool == UBStylusTool::Eraser) @@ -855,10 +834,36 @@ void UBGraphicsScene::drawLineTo(const QPointF &pEndPoint, const qreal &startWid if (initialWidth == endWidth) initialWidth = mPreviousWidth; - // UBGraphicsPolygonItem *polygonItem = lineToPolygonItem(QLineF(mPreviousPoint, pEndPoint), pWidth); + if (bLineStyle) { + QSetIterator itItems(mAddedItems); + + while (itItems.hasNext()) { + QGraphicsItem* item = itItems.next(); + removeItem(item); + } + mAddedItems.clear(); + } UBGraphicsPolygonItem *polygonItem = lineToPolygonItem(QLineF(mPreviousPoint, pEndPoint), initialWidth, endWidth); + addPolygonItemToCurrentStroke(polygonItem); + + if (!bLineStyle) { + mPreviousPoint = pEndPoint; + mPreviousWidth = endWidth; + } +} + +void UBGraphicsScene::drawCurve(const QList& points, qreal startWidth, qreal endWidth) +{ + UBGraphicsPolygonItem* polygonItem = curveToPolygonItem(points, startWidth, endWidth); + addPolygonItemToCurrentStroke(polygonItem); + + mPreviousWidth = endWidth; + mPreviousPoint = points.last(); +} +void UBGraphicsScene::addPolygonItemToCurrentStroke(UBGraphicsPolygonItem* polygonItem) +{ if (!polygonItem->brush().isOpaque()) { // ------------------------------------------------------------------------------------- @@ -871,19 +876,6 @@ void UBGraphicsScene::drawLineTo(const QPointF &pEndPoint, const qreal &startWid } } - - if (bLineStyle) - { - QSetIterator itItems(mAddedItems); - - while (itItems.hasNext()) - { - QGraphicsItem* item = itItems.next(); - removeItem(item); - } - mAddedItems.clear(); - } - mpLastPolygon = polygonItem; mAddedItems.insert(polygonItem); @@ -896,12 +888,6 @@ void UBGraphicsScene::drawLineTo(const QPointF &pEndPoint, const qreal &startWid mPreviousPolygonItems.append(polygonItem); - - if (!bLineStyle) - { - mPreviousPoint = pEndPoint; - mPreviousWidth = endWidth; - } } void UBGraphicsScene::eraseLineTo(const QPointF &pEndPoint, const qreal &pWidth) @@ -1149,6 +1135,14 @@ UBGraphicsPolygonItem* UBGraphicsScene::arcToPolygonItem(const QLineF& pStartRad return polygonToPolygonItem(polygon); } +UBGraphicsPolygonItem* UBGraphicsScene::curveToPolygonItem(const QList& points, qreal startWidth, qreal endWidth) +{ + QPolygonF polygon = UBGeometryUtils::curveToPolygon(points, startWidth, endWidth); + + return polygonToPolygonItem(polygon); + +} + void UBGraphicsScene::clearSelectionFrame() { if (mSelectionFrame) { diff --git a/src/domain/UBGraphicsScene.h b/src/domain/UBGraphicsScene.h index 19571d62..a62d66e7 100644 --- a/src/domain/UBGraphicsScene.h +++ b/src/domain/UBGraphicsScene.h @@ -194,6 +194,7 @@ class UBGraphicsScene: public UBCoreGraphicsScene, public UBItem void drawLineTo(const QPointF& pEndPoint, const qreal& pStartWidth, const qreal& endWidth, bool bLineStyle); void eraseLineTo(const QPointF& pEndPoint, const qreal& pWidth); void drawArcTo(const QPointF& pCenterPoint, qreal pSpanAngle); + void drawCurve(const QList& points, qreal startWidth, qreal endWidth); bool isEmpty() const; @@ -357,6 +358,8 @@ public slots: UBGraphicsPolygonItem* lineToPolygonItem(const QLineF &pLine, const qreal &pStartWidth, const qreal &pEndWidth); UBGraphicsPolygonItem* arcToPolygonItem(const QLineF& pStartRadius, qreal pSpanAngle, qreal pWidth); + UBGraphicsPolygonItem* curveToPolygonItem(const QList& points, qreal startWidth, qreal endWidth); + void addPolygonItemToCurrentStroke(UBGraphicsPolygonItem* polygonItem); void initPolygonItem(UBGraphicsPolygonItem*); diff --git a/src/domain/UBGraphicsStroke.cpp b/src/domain/UBGraphicsStroke.cpp index 9208315c..90db6dde 100644 --- a/src/domain/UBGraphicsStroke.cpp +++ b/src/domain/UBGraphicsStroke.cpp @@ -70,19 +70,6 @@ QList UBGraphicsStroke::addPoint(const QPointF& point, UBInterpolator:: { int n = mDrawnPoints.size(); - /* - if (n > 0) { - qreal MIN_DISTANCE = 3; - QPointF lastPoint = mDrawnPoints.last(); - qreal distance = QLineF(lastPoint, point).length(); - - //qDebug() << "distance: " << distance; - - if (distance < MIN_DISTANCE) - return QList(); - } - */ - if (interpolationMethod == UBInterpolator::NoInterpolation || n == 0) { mDrawnPoints << point; mAllPoints << point; @@ -92,14 +79,20 @@ QList UBGraphicsStroke::addPoint(const QPointF& point, UBInterpolator:: else if (interpolationMethod == UBInterpolator::Bezier) { // This is a bit special, as the curve we are interpolating is not between two drawn points; // it is between the midway points of the second-to-last and last point, and last and current point. - qreal MIN_DISTANCE = 3; + qreal MIN_DISTANCE = 3; // TODO: make this dependant on zoom QPointF lastPoint = mDrawnPoints.last(); qreal distance = QLineF(lastPoint, point).length(); //qDebug() << "distance: " << distance; - if (distance < MIN_DISTANCE) + // We don't draw anything below the minimum distance. For performance reasons but also to make the curve + // look smoother (e.g shaking slightly won't affect the curve). + if (distance < MIN_DISTANCE) { + // we still keep track of that point to calculate the distance correctly next time around + mDrawnPoints << point; return QList(); + } + if (n == 1) { // We start with a straight line to the first midway point QPointF lastPoint = mDrawnPoints[0]; @@ -119,7 +112,7 @@ QList UBGraphicsStroke::addPoint(const QPointF& point, UBInterpolator:: bz.setPoints(startPoint, p1, endPoint); - QList newPoints = bz.getPoints(7); + QList newPoints = bz.getPoints(10); foreach(QPointF p, newPoints) { mAllPoints << p; @@ -127,8 +120,6 @@ QList UBGraphicsStroke::addPoint(const QPointF& point, UBInterpolator:: mDrawnPoints << point; return newPoints; - - } else { @@ -149,7 +140,7 @@ QList UBGraphicsStroke::addPoint(const QPointF& point, UBInterpolator:: sp.setPoints(mDrawnPoints[n-2], mDrawnPoints[n-1], point); */ - // todo: better way of avoiding problems with constant x's. in the meantime this'll do. + // todo: better way of avoiding problems with equal x's (such as PCA). in the meantime this'll do. qreal x0 = mDrawnPoints[n-3].x(); qreal x1 = mDrawnPoints[n-2].x(); qreal x2 = mDrawnPoints[n-1].x(); diff --git a/src/domain/UBGraphicsStroke.h b/src/domain/UBGraphicsStroke.h index 2f9b2759..2f9f4eae 100644 --- a/src/domain/UBGraphicsStroke.h +++ b/src/domain/UBGraphicsStroke.h @@ -70,7 +70,10 @@ class UBGraphicsStroke QList mPolygons; + /// Points that were drawn (actually received through input device) QList mDrawnPoints; + + /// All the points (including interpolated) that are used to draw the stroke QList mAllPoints; }; diff --git a/src/frameworks/UBGeometryUtils.cpp b/src/frameworks/UBGeometryUtils.cpp index ea5f22b2..fb3392e7 100644 --- a/src/frameworks/UBGeometryUtils.cpp +++ b/src/frameworks/UBGeometryUtils.cpp @@ -242,6 +242,111 @@ QPolygonF UBGeometryUtils::arcToPolygon(const QLineF& startRadius, qreal spanAng return painterPath.toFillPolygon(); } +/** + * @brief Build and return a polygon from a list of points (at least 2), and start and end widths. + * + * The resulting polygon will pass by all points in the curve; its thickness is calculated at each point + * of the curve (linearly interpolated between start and end widths) and the segments are joined by + * (approximately) curved joints. + * + * Like with lineToPolygon, the ends are semi-circular. + */ +QPolygonF UBGeometryUtils::curveToPolygon(const QList& points, qreal startWidth, qreal endWidth) +{ + typedef QPair pointPair; + + int n_points = points.size(); + + if (n_points < 2) + return QPolygonF(); + + if (n_points == 2) + return lineToPolygon(points[0], points[1], startWidth, endWidth); + + /* The vertices (x's) are calculated based on the stroke's width and angle, and the position of the + supplied points (o's): + + x----------x--------x + + o o o + + x----------x -------x + + The vertices above and below each 'o' point are temporarily stored together, + as a pair of points. + + */ + QList newPoints; + + + QLineF firstSegment = QLineF(points[0], points[1]); + QLineF normal = firstSegment.normalVector(); + normal.setLength(startWidth/2.0); + newPoints << pointPair(normal.p2(), points[0] - QPointF(normal.dx(), normal.dy())); + + /* + Calculating the vertices (d1 and d2, below) is a little less trivial for the + next points: their positions depend on the angle between one segment and the next. + + d1 + ------------x + \ + .a b . \ + \ + --------x \ + d2 \ \ + \ .c \ + + Here, points a, b and c are supplied in the `points` list. + + N.B: The drawing isn't quite accurate; we don't do a miter joint but a kind + of rounded-off joint (the distance between b and d1 is half the width of the stroke) + */ + + for (int i(1); i < n_points-1; ++i) { + qreal width = startWidth + (qreal(i)/qreal(n_points-1)) * (endWidth - startWidth); + + QLineF normal = (QLineF(points[i-1], points[i+1])).normalVector(); + normal.setLength(width/2.0); + QPointF d1 = points[i] + QPointF(normal.dx(), normal.dy()); + QPointF d2 = points[i] - QPointF(normal.dx(), normal.dy()); + + newPoints << pointPair(d1, d2); + } + + // The last point is similar to the first + QLineF lastSegment = QLineF(points[n_points-2], points[n_points-1]); + normal = lastSegment.normalVector(); + normal.setLength(endWidth/2.0); + + QPointF d1 = points.last() + QPointF(normal.dx(), normal.dy()); + QPointF d2 = points.last() - QPointF(normal.dx(), normal.dy()); + + newPoints << pointPair(d1, d2); + + QPainterPath path; + path.moveTo(newPoints[0].first); + + for (int i(1); i < n_points; ++i) { + path.lineTo(newPoints[i].first); + } + + path.arcTo(points.last().x() - endWidth/2.0, points.last().y() - endWidth/2.0, endWidth, endWidth, (90.0 + lastSegment.angle()), -180.0); + //path.lineTo(newPoints.last().second); + + for (int i(n_points-1); i >= 0; --i) { + path.lineTo(newPoints[i].second); + } + + path.arcTo(points[0].x() - startWidth/2.0, points[0].y() - startWidth/2.0, startWidth, startWidth, (firstSegment.angle() - 90.0), -180.0); + //path.lineTo(newPoints[0].second); + + + path.closeSubpath(); + + return path.toFillPolygon(); +} + QPointF UBGeometryUtils::pointConstrainedInRect(QPointF point, QRectF rect) { return QPointF(qMax(rect.x(), qMin(rect.x() + rect.width(), point.x())), qMax(rect.y(), qMin(rect.y() + rect.height(), point.y()))); diff --git a/src/frameworks/UBGeometryUtils.h b/src/frameworks/UBGeometryUtils.h index b54cfe97..fef01891 100644 --- a/src/frameworks/UBGeometryUtils.h +++ b/src/frameworks/UBGeometryUtils.h @@ -45,6 +45,7 @@ class UBGeometryUtils static QPolygonF lineToPolygon(const QPointF& pStart, const QPointF& pEnd, const qreal& pStartWidth, const qreal& pEndWidth); + static QPolygonF curveToPolygon(const QList& points, qreal startWidth, qreal endWidth); static QPointF pointConstrainedInRect(QPointF point, QRectF rect); static QPoint pointConstrainedInRect(QPoint point, QRect rect); From 1ef73a70b87edb6eb6b065014b1a68c8fde2d2e2 Mon Sep 17 00:00:00 2001 From: Craig Watson Date: Tue, 5 Apr 2016 15:27:31 +0200 Subject: [PATCH 5/9] Strokes: corrected tracking of last received point --- src/domain/UBGraphicsStroke.cpp | 7 ++++--- src/domain/UBGraphicsStroke.h | 2 ++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/domain/UBGraphicsStroke.cpp b/src/domain/UBGraphicsStroke.cpp index 90db6dde..3553a465 100644 --- a/src/domain/UBGraphicsStroke.cpp +++ b/src/domain/UBGraphicsStroke.cpp @@ -71,6 +71,7 @@ QList UBGraphicsStroke::addPoint(const QPointF& point, UBInterpolator:: int n = mDrawnPoints.size(); if (interpolationMethod == UBInterpolator::NoInterpolation || n == 0) { + mLastReceivedPoint = point; mDrawnPoints << point; mAllPoints << point; return QList() << point; @@ -80,8 +81,7 @@ QList UBGraphicsStroke::addPoint(const QPointF& point, UBInterpolator:: // This is a bit special, as the curve we are interpolating is not between two drawn points; // it is between the midway points of the second-to-last and last point, and last and current point. qreal MIN_DISTANCE = 3; // TODO: make this dependant on zoom - QPointF lastPoint = mDrawnPoints.last(); - qreal distance = QLineF(lastPoint, point).length(); + qreal distance = QLineF(mLastReceivedPoint, point).length(); //qDebug() << "distance: " << distance; @@ -89,7 +89,7 @@ QList UBGraphicsStroke::addPoint(const QPointF& point, UBInterpolator:: // look smoother (e.g shaking slightly won't affect the curve). if (distance < MIN_DISTANCE) { // we still keep track of that point to calculate the distance correctly next time around - mDrawnPoints << point; + mLastReceivedPoint = point; return QList(); } @@ -101,6 +101,7 @@ QList UBGraphicsStroke::addPoint(const QPointF& point, UBInterpolator:: return QList() << ((lastPoint + point)/2.0); } + QPointF p0 = mDrawnPoints[mDrawnPoints.size() - 2]; QPointF p1 = mDrawnPoints[mDrawnPoints.size() - 1]; QPointF p2 = point; diff --git a/src/domain/UBGraphicsStroke.h b/src/domain/UBGraphicsStroke.h index 2f9f4eae..5ef91507 100644 --- a/src/domain/UBGraphicsStroke.h +++ b/src/domain/UBGraphicsStroke.h @@ -76,6 +76,8 @@ class UBGraphicsStroke /// All the points (including interpolated) that are used to draw the stroke QList mAllPoints; + QPointF mLastReceivedPoint; + }; #endif /* UBGRAPHICSSTROKE_H_ */ From 999fcec917d5c014155f4fa358f20969ceda01bf Mon Sep 17 00:00:00 2001 From: Craig Watson Date: Mon, 11 Apr 2016 14:13:24 +0200 Subject: [PATCH 6/9] Removed spline interpolators --- OpenBoard.pro | 3 - src/domain/UBGraphicsScene.cpp | 24 ------- src/domain/UBGraphicsStroke.cpp | 104 +----------------------------- src/domain/UBGraphicsStroke.h | 2 - src/frameworks/UBInterpolator.cpp | 86 ------------------------ src/frameworks/UBInterpolator.h | 57 +--------------- 6 files changed, 5 insertions(+), 271 deletions(-) diff --git a/OpenBoard.pro b/OpenBoard.pro index 13b005e6..4558b666 100644 --- a/OpenBoard.pro +++ b/OpenBoard.pro @@ -66,9 +66,6 @@ INCLUDEPATH += src/pdf-merger include(src/pdf-merger/pdfMerger.pri) #ThirdParty -INCLUDEPATH += $$THIRD_PARTY_PATH/spline/ -INCLUDEPATH += $$THIRD_PARTY_PATH/alglib/ -include($$THIRD_PARTY_PATH/alglib/alglib.pri) DEPENDPATH += $$THIRD_PARTY_PATH/quazip/ INCLUDEPATH += $$THIRD_PARTY_PATH/quazip/ include($$THIRD_PARTY_PATH/quazip/quazip.pri) diff --git a/src/domain/UBGraphicsScene.cpp b/src/domain/UBGraphicsScene.cpp index d19a9457..b9d18043 100644 --- a/src/domain/UBGraphicsScene.cpp +++ b/src/domain/UBGraphicsScene.cpp @@ -630,30 +630,6 @@ bool UBGraphicsScene::inputDeviceRelease() mDrawWithCompass = false; } else if (mCurrentStroke){ - /* - // ------------------------------------------------------------------------- - // Replace the stroke by a smoother one - // ------------------------------------------------------------------------- - UBGraphicsStroke* smoothStroke = mCurrentStroke->smoothe(); - - foreach(UBGraphicsPolygonItem* poly, mCurrentStroke->polygons()){ - mPreviousPolygonItems.removeAll(poly); - removeItem(poly); - } - delete mCurrentStroke; - mCurrentStroke = smoothStroke; - - moveTo(smoothStroke->points()[0]); - for (int i(1); i < smoothStroke->points().size(); ++i) { - QPointF currentPoint = smoothStroke->points()[i]; - - drawLineTo(currentPoint, dc->currentToolWidth(), dc->currentToolWidth(), false); - moveTo(currentPoint); - } - - // ------------------------------------------------------------------------- - */ - UBGraphicsStrokesGroup* pStrokes = new UBGraphicsStrokesGroup(); // Remove the strokes that were just drawn here and replace them by a stroke item diff --git a/src/domain/UBGraphicsStroke.cpp b/src/domain/UBGraphicsStroke.cpp index 3553a465..6e28e9ca 100644 --- a/src/domain/UBGraphicsStroke.cpp +++ b/src/domain/UBGraphicsStroke.cpp @@ -88,7 +88,7 @@ QList UBGraphicsStroke::addPoint(const QPointF& point, UBInterpolator:: // We don't draw anything below the minimum distance. For performance reasons but also to make the curve // look smoother (e.g shaking slightly won't affect the curve). if (distance < MIN_DISTANCE) { - // we still keep track of that point to calculate the distance correctly next time around + // but we still keep track of that point to calculate the distance correctly next time around mLastReceivedPoint = point; return QList(); } @@ -123,59 +123,7 @@ QList UBGraphicsStroke::addPoint(const QPointF& point, UBInterpolator:: return newPoints; } - else { - - qreal MIN_INTERPOLATION_DISTANCE = 0; - - QPointF lastPoint; - if (!mDrawnPoints.isEmpty()) - lastPoint = mDrawnPoints.last(); - - QList newPoints; - - // Interpolation - if (n > 3 && QLineF(lastPoint, point).length() > MIN_INTERPOLATION_DISTANCE) { - - /* - UBSimpleSpline sp; - sp.setPoints(mDrawnPoints[n-2], mDrawnPoints[n-1], point); - */ - - // todo: better way of avoiding problems with equal x's (such as PCA). in the meantime this'll do. - qreal x0 = mDrawnPoints[n-3].x(); - qreal x1 = mDrawnPoints[n-2].x(); - qreal x2 = mDrawnPoints[n-1].x(); - qreal x3 = point.x(); - if (!(x0 == x1 || x0 == x2 || x0 == x3 || x1 == x2 || x1 == x3 || x2 == x3)) { - - UBCatmullRomSpline sp; - sp.setPoints(QList() << mDrawnPoints[n-3] << mDrawnPoints[n-2] << mDrawnPoints[n-1] << point); - - // get an extra 3 values in between the current point and last one - int n_points = 3; // number of points to interpolate - double interval = (point.x() - lastPoint.x())/double(n_points+1); - - qDebug() << "Interpolating between: " << lastPoint << " and " << point; - - for (int i(1); i <= n_points; ++i) { - double x = lastPoint.x() + i*interval; - QPointF newPoint(x, sp.y(x)); - qDebug() << newPoint; - - newPoints << newPoint; - mAllPoints << newPoint; - //qDebug() << "Got new point: " << newPoint; - } - } - - } - - newPoints << point; - mAllPoints << point; - mDrawnPoints << point; - - return newPoints; - } + return QList(); } bool UBGraphicsStroke::hasPressure() @@ -220,51 +168,3 @@ void UBGraphicsStroke::clear() mPolygons.clear(); } } - - -/** - * @brief Smoothe the curve, by interpolating extra points where needed. - * - * @return A new stroke based on the current one. - */ -UBGraphicsStroke* UBGraphicsStroke::smoothe() -{ - // Catmull-Rom spline interpolation - UBCatmullRomSpline sp; - - UBGraphicsStroke * smoothStroke = new UBGraphicsStroke(); - - smoothStroke->mAllPoints << mAllPoints[0]; - - for (int i(0); i < mAllPoints.size() - 3; ++i) { - QPointF p1, p2; - - p1 = mAllPoints[i+1]; - p2 = mAllPoints[i+2]; - - qreal x0 = mAllPoints[i].x(); - qreal x1 = p1.x(); - qreal x2 = p2.x(); - qreal x3 = mAllPoints[i+3].x(); - - if (!(x0 == x1 || x0 == x2 || x0 == x3 || x1 == x2 || x1 == x3 || x2 == x3)) { - sp.setPoints(QList() << mAllPoints[i] << mAllPoints[i+1] << mAllPoints[i+2] << mAllPoints[i+3]); - - smoothStroke->mAllPoints << mAllPoints[i+1]; - int n_points = 3; // number of points to interpolate - double interval = (p2.x() - p1.x())/double(n_points+1); - - for (int i(1); i <= n_points; ++i) { - double x = p1.x() + i*interval; - QPointF newPoint(x, sp.y(x)); - - smoothStroke->mAllPoints << newPoint; - } - } - - } - smoothStroke->mAllPoints << mAllPoints[mAllPoints.size() - 2] << mAllPoints[mAllPoints.size() - 1]; - - - return smoothStroke; -} diff --git a/src/domain/UBGraphicsStroke.h b/src/domain/UBGraphicsStroke.h index 5ef91507..860ab8f1 100644 --- a/src/domain/UBGraphicsStroke.h +++ b/src/domain/UBGraphicsStroke.h @@ -59,8 +59,6 @@ class UBGraphicsStroke QList addPoint(const QPointF& point, UBInterpolator::InterpolationMethod interpolationMethod = UBInterpolator::NoInterpolation); - UBGraphicsStroke* smoothe(); - const QList& points() { return mAllPoints; } protected: diff --git a/src/frameworks/UBInterpolator.cpp b/src/frameworks/UBInterpolator.cpp index bd40b038..b5777bcd 100644 --- a/src/frameworks/UBInterpolator.cpp +++ b/src/frameworks/UBInterpolator.cpp @@ -8,92 +8,6 @@ UBInterpolator::~UBInterpolator() { } - -UBSimpleSpline::UBSimpleSpline() -{ - -} - -void UBSimpleSpline::setPoints(QList points) -{ - setPoints(points[0], points[1], points[2]); -} - -void UBSimpleSpline::setPoints(QPointF p0, QPointF p1, QPointF p2) -{ - /* - p0 -= p0; - p1 -= p0; - p2 -= p0; - */ - long double x0, x1, x2, y0, y1, y2; - x0 = p0.x(); - x1 = p1.x(); - x2 = p2.x(); - y0 = p0.y(); - y1 = p1.y(); - y2 = p2.y(); - - long double k1 = (y2-y0)/(x2-x0); - - m_a = (y1-y2-k1*(x1-x2))/(pow(x1,3) - pow(x2,3) - 3*x2*(pow(x1,2)-pow(x2,2)) - - 3*(pow(x1,2) - 2*x1*x2)*(x1-x2)); - - m_b = -3*m_a*x2; - m_c = k1 - 3*m_a*pow(x1,2) - 2*m_b*x1; - m_d = y1 - m_a*pow(x1,3) - m_b*pow(x1,2) - m_c*x1; -} - -double UBSimpleSpline::y(double x) -{ - return m_a*pow(x, 3) + m_b*pow(x, 2) + m_c*x + m_d; -} - -UBCatmullRomSpline::UBCatmullRomSpline() -{ - mInterpolant = 0; -} - -UBCatmullRomSpline::~UBCatmullRomSpline() -{ - if (mInterpolant) - delete mInterpolant; -} - -void UBCatmullRomSpline::setPoints(QList points) -{ - // todo : basis change to avoid crashing when several X's are equal - mInterpolant = new alglib::spline1dinterpolant(); - - // alglib arrays are defined as strings - QString x = "["; - QString y = "["; - - foreach(QPointF point, points) { - x += (QString::number(point.x()) + QString(",")); - y += (QString::number(point.y()) + QString(",")); - } - - x.chop(1); - y.chop(1); - - x+="]"; - y+="]"; - - alglib::real_1d_array xArray = x.toLatin1().data(); - alglib::real_1d_array yArray = y.toLatin1().data(); - - alglib::spline1dbuildcatmullrom(xArray, yArray, *mInterpolant); - - -} - -double UBCatmullRomSpline::y(double x) -{ - return alglib::spline1dcalc(*mInterpolant, x); -} - - UBQuadraticBezier::UBQuadraticBezier() { mPath = 0; diff --git a/src/frameworks/UBInterpolator.h b/src/frameworks/UBInterpolator.h index 1a028523..9a714488 100644 --- a/src/frameworks/UBInterpolator.h +++ b/src/frameworks/UBInterpolator.h @@ -3,9 +3,6 @@ #include -#include "spline.h" -#include "interpolation.h" - class UBInterpolator { /* Abstract class representing an interpolator */ @@ -13,8 +10,8 @@ class UBInterpolator public: enum InterpolationMethod { NoInterpolation, - SimpleSpline, - CatmullRom, + //SimpleSpline, + //CatmullRom, Bezier }; @@ -22,58 +19,10 @@ public: virtual ~UBInterpolator(); virtual void setPoints(QList points) = 0; - virtual double y(double x) {} - -}; - - -class UBSimpleSpline : public UBInterpolator -{ - /* A basic cubic spline interpolator, that requires only three - * points to interpolate between the second and third one. - * To do so, the curvature at p2 is set to 0, so the resulting - * curve is not very smooth. - * However, it is better than linear interpolation and requires no - * "future" points, so it can be used seamlessly during drawing. - */ - -public: - UBSimpleSpline(); - virtual ~UBSimpleSpline() {} - - virtual void setPoints(QList points); - void setPoints(QPointF p0, QPointF p1, QPointF p2); - - virtual double y(double x); - -private: - long double m_a, - m_b, - m_c, - m_d; -}; - - -class UBCatmullRomSpline : public UBInterpolator -{ - /* Catmull-Rom spline, using AlgLib as backend - * - * This requires four points to interpolate between the middle two. - */ - -public: - UBCatmullRomSpline(); - virtual ~UBCatmullRomSpline(); - - virtual void setPoints(QList points); - virtual double y(double x); - -private: - alglib::spline1dinterpolant * mInterpolant; + //virtual double y(double x) {} }; - class UBQuadraticBezier : public UBInterpolator { From f82a0a32d3f674657013ce081468bd0ceb01ba0b Mon Sep 17 00:00:00 2001 From: Craig Watson Date: Mon, 11 Apr 2016 15:29:19 +0200 Subject: [PATCH 7/9] Fixed calculation of minimum distance for stroke polygons' drawing --- src/domain/UBGraphicsScene.cpp | 5 ++--- src/domain/UBGraphicsStroke.cpp | 21 +++++++++------------ src/domain/UBGraphicsStroke.h | 5 ++--- 3 files changed, 13 insertions(+), 18 deletions(-) diff --git a/src/domain/UBGraphicsScene.cpp b/src/domain/UBGraphicsScene.cpp index b9d18043..465b67ef 100644 --- a/src/domain/UBGraphicsScene.cpp +++ b/src/domain/UBGraphicsScene.cpp @@ -558,10 +558,9 @@ bool UBGraphicsScene::inputDeviceMove(const QPointF& scenePos, const qreal& pres } QList newPoints = mCurrentStroke->addPoint(scenePos, interpolator); - if (newPoints.length() > 1) + if (newPoints.length() > 1) { drawCurve(newPoints, mPreviousWidth, width); - else - drawLineTo(scenePos, width, UBDrawingController::drawingController()->stylusTool() == UBStylusTool::Line); + } } } else if (currentTool == UBStylusTool::Eraser) diff --git a/src/domain/UBGraphicsStroke.cpp b/src/domain/UBGraphicsStroke.cpp index 6e28e9ca..bad4272a 100644 --- a/src/domain/UBGraphicsStroke.cpp +++ b/src/domain/UBGraphicsStroke.cpp @@ -29,12 +29,14 @@ #include "UBGraphicsPolygonItem.h" +#include "board/UBBoardController.h" +#include "core/UBApplication.h" #include "core/memcheck.h" UBGraphicsStroke::UBGraphicsStroke() { - // NOOP + mAntiScaleRatio = 1./(UBApplication::boardController->systemScaleFactor() * UBApplication::boardController->currentZoom()); } @@ -71,7 +73,6 @@ QList UBGraphicsStroke::addPoint(const QPointF& point, UBInterpolator:: int n = mDrawnPoints.size(); if (interpolationMethod == UBInterpolator::NoInterpolation || n == 0) { - mLastReceivedPoint = point; mDrawnPoints << point; mAllPoints << point; return QList() << point; @@ -80,28 +81,24 @@ QList UBGraphicsStroke::addPoint(const QPointF& point, UBInterpolator:: else if (interpolationMethod == UBInterpolator::Bezier) { // This is a bit special, as the curve we are interpolating is not between two drawn points; // it is between the midway points of the second-to-last and last point, and last and current point. - qreal MIN_DISTANCE = 3; // TODO: make this dependant on zoom - qreal distance = QLineF(mLastReceivedPoint, point).length(); - //qDebug() << "distance: " << distance; + // Don't draw segments smaller than a certain length. This can help with performance + // (less polygons in a stroke) but mostly with keeping the curve smooth. + qreal MIN_DISTANCE = 3*mAntiScaleRatio; + qreal distance = QLineF(mDrawnPoints.last(), point).length(); - // We don't draw anything below the minimum distance. For performance reasons but also to make the curve - // look smoother (e.g shaking slightly won't affect the curve). if (distance < MIN_DISTANCE) { - // but we still keep track of that point to calculate the distance correctly next time around - mLastReceivedPoint = point; return QList(); } + // The first segment is just a straight line to the first midway point if (n == 1) { - // We start with a straight line to the first midway point QPointF lastPoint = mDrawnPoints[0]; mDrawnPoints << point; - return QList() << ((lastPoint + point)/2.0); + return QList() << lastPoint << ((lastPoint + point)/2.0); } - QPointF p0 = mDrawnPoints[mDrawnPoints.size() - 2]; QPointF p1 = mDrawnPoints[mDrawnPoints.size() - 1]; QPointF p2 = point; diff --git a/src/domain/UBGraphicsStroke.h b/src/domain/UBGraphicsStroke.h index 860ab8f1..5701eda2 100644 --- a/src/domain/UBGraphicsStroke.h +++ b/src/domain/UBGraphicsStroke.h @@ -68,14 +68,13 @@ class UBGraphicsStroke QList mPolygons; - /// Points that were drawn (actually received through input device) + /// Points that were drawn by the user (actually received through input device) QList mDrawnPoints; /// All the points (including interpolated) that are used to draw the stroke QList mAllPoints; - QPointF mLastReceivedPoint; - + qreal mAntiScaleRatio; }; #endif /* UBGRAPHICSSTROKE_H_ */ From b55ab7c92ceccd5fd632a229d67690f465bc6125 Mon Sep 17 00:00:00 2001 From: Craig Watson Date: Mon, 11 Apr 2016 15:39:18 +0200 Subject: [PATCH 8/9] Corrected stroke drawing when no interpolation is used (so UBGraphicsScene::drawCurve can be used either way) --- src/domain/UBGraphicsStroke.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/domain/UBGraphicsStroke.cpp b/src/domain/UBGraphicsStroke.cpp index bad4272a..40516174 100644 --- a/src/domain/UBGraphicsStroke.cpp +++ b/src/domain/UBGraphicsStroke.cpp @@ -72,10 +72,17 @@ QList UBGraphicsStroke::addPoint(const QPointF& point, UBInterpolator:: { int n = mDrawnPoints.size(); - if (interpolationMethod == UBInterpolator::NoInterpolation || n == 0) { + if (n == 0) { mDrawnPoints << point; mAllPoints << point; - return QList() << point; + return QList(); + } + + if (interpolationMethod == UBInterpolator::NoInterpolation) { + QPointF lastPoint = mDrawnPoints.last(); + mDrawnPoints << point; + mAllPoints << point; + return QList() << lastPoint << point; } else if (interpolationMethod == UBInterpolator::Bezier) { From 43e0e414c9a21dac368905c3dcecedc8217bd992 Mon Sep 17 00:00:00 2001 From: Craig Watson Date: Tue, 12 Apr 2016 15:54:02 +0200 Subject: [PATCH 9/9] Draw last (half-) segment of stroke to make drawing feel smoother Also, clarified the difference between received and drawn points in UBGraphicsStroke --- src/domain/UBGraphicsScene.cpp | 25 +++++++++++++++++++++++++ src/domain/UBGraphicsScene.h | 1 + src/domain/UBGraphicsStroke.cpp | 31 ++++++++++++++++++------------- src/domain/UBGraphicsStroke.h | 8 ++++---- 4 files changed, 48 insertions(+), 17 deletions(-) diff --git a/src/domain/UBGraphicsScene.cpp b/src/domain/UBGraphicsScene.cpp index 465b67ef..718505d5 100644 --- a/src/domain/UBGraphicsScene.cpp +++ b/src/domain/UBGraphicsScene.cpp @@ -335,6 +335,7 @@ UBGraphicsScene::UBGraphicsScene(UBDocumentProxy* parent, bool enableUndoRedoSta , mZLayerController(new UBZLayerController(this)) , mpLastPolygon(NULL) , mCurrentPolygon(0) + , mTempPolygon(NULL) , mSelectionFrame(0) { UBCoreGraphicsScene::setObjectName("BoardScene"); @@ -561,6 +562,23 @@ bool UBGraphicsScene::inputDeviceMove(const QPointF& scenePos, const qreal& pres if (newPoints.length() > 1) { drawCurve(newPoints, mPreviousWidth, width); } + + if (interpolator == UBInterpolator::Bezier) { + // Bezier curves aren't drawn all the way to the scenePos (they stop halfway between the previous and + // current scenePos), so we add a line from the last drawn position in the stroke and the + // scenePos, to make the drawing feel more responsive. This line is then deleted if a new segment is + // added to the stroke. (Or it is added to the stroke when we stop drawing) + + if (mTempPolygon) { + removeItem(mTempPolygon); + mTempPolygon = NULL; + } + + QPointF lastDrawnPoint = newPoints.last(); + + mTempPolygon = lineToPolygonItem(QLineF(lastDrawnPoint, scenePos), mPreviousWidth, width); + addItem(mTempPolygon); + } } } else if (currentTool == UBStylusTool::Eraser) @@ -629,6 +647,13 @@ bool UBGraphicsScene::inputDeviceRelease() mDrawWithCompass = false; } else if (mCurrentStroke){ + if (mTempPolygon) { + UBGraphicsPolygonItem * poly = dynamic_cast(mTempPolygon->deepCopy()); + removeItem(mTempPolygon); + mTempPolygon = NULL; + addPolygonItemToCurrentStroke(poly); + } + UBGraphicsStrokesGroup* pStrokes = new UBGraphicsStrokesGroup(); // Remove the strokes that were just drawn here and replace them by a stroke item diff --git a/src/domain/UBGraphicsScene.h b/src/domain/UBGraphicsScene.h index a62d66e7..ea04ce9b 100644 --- a/src/domain/UBGraphicsScene.h +++ b/src/domain/UBGraphicsScene.h @@ -442,6 +442,7 @@ public slots: UBZLayerController *mZLayerController; UBGraphicsPolygonItem* mpLastPolygon; + UBGraphicsPolygonItem* mTempPolygon; bool mDrawWithCompass; UBGraphicsPolygonItem *mCurrentPolygon; diff --git a/src/domain/UBGraphicsStroke.cpp b/src/domain/UBGraphicsStroke.cpp index 40516174..df02796b 100644 --- a/src/domain/UBGraphicsStroke.cpp +++ b/src/domain/UBGraphicsStroke.cpp @@ -70,18 +70,18 @@ QList UBGraphicsStroke::polygons() const */ QList UBGraphicsStroke::addPoint(const QPointF& point, UBInterpolator::InterpolationMethod interpolationMethod) { - int n = mDrawnPoints.size(); + int n = mReceivedPoints.size(); if (n == 0) { + mReceivedPoints << point; mDrawnPoints << point; - mAllPoints << point; return QList(); } if (interpolationMethod == UBInterpolator::NoInterpolation) { - QPointF lastPoint = mDrawnPoints.last(); + QPointF lastPoint = mReceivedPoints.last(); + mReceivedPoints << point; mDrawnPoints << point; - mAllPoints << point; return QList() << lastPoint << point; } @@ -91,23 +91,24 @@ QList UBGraphicsStroke::addPoint(const QPointF& point, UBInterpolator:: // Don't draw segments smaller than a certain length. This can help with performance // (less polygons in a stroke) but mostly with keeping the curve smooth. - qreal MIN_DISTANCE = 3*mAntiScaleRatio; - qreal distance = QLineF(mDrawnPoints.last(), point).length(); + qreal MIN_DISTANCE = 5*mAntiScaleRatio; + qreal distance = QLineF(mReceivedPoints.last(), point).length(); if (distance < MIN_DISTANCE) { - return QList(); + return QList() << mDrawnPoints.last(); } // The first segment is just a straight line to the first midway point if (n == 1) { - QPointF lastPoint = mDrawnPoints[0]; - mDrawnPoints << point; + QPointF lastPoint = mReceivedPoints[0]; + mReceivedPoints << point; + mDrawnPoints << QPointF((lastPoint + point)/2.0); return QList() << lastPoint << ((lastPoint + point)/2.0); } - QPointF p0 = mDrawnPoints[mDrawnPoints.size() - 2]; - QPointF p1 = mDrawnPoints[mDrawnPoints.size() - 1]; + QPointF p0 = mReceivedPoints[mReceivedPoints.size() - 2]; + QPointF p1 = mReceivedPoints[mReceivedPoints.size() - 1]; QPointF p2 = point; UBQuadraticBezier bz; @@ -119,11 +120,15 @@ QList UBGraphicsStroke::addPoint(const QPointF& point, UBInterpolator:: QList newPoints = bz.getPoints(10); + // avoid adding duplicates + if (newPoints.first() == mDrawnPoints.last()) + mDrawnPoints.removeLast(); + foreach(QPointF p, newPoints) { - mAllPoints << p; + mDrawnPoints << p; } - mDrawnPoints << point; + mReceivedPoints << point; return newPoints; } diff --git a/src/domain/UBGraphicsStroke.h b/src/domain/UBGraphicsStroke.h index 5701eda2..e5f6d38e 100644 --- a/src/domain/UBGraphicsStroke.h +++ b/src/domain/UBGraphicsStroke.h @@ -59,7 +59,7 @@ class UBGraphicsStroke QList addPoint(const QPointF& point, UBInterpolator::InterpolationMethod interpolationMethod = UBInterpolator::NoInterpolation); - const QList& points() { return mAllPoints; } + const QList& points() { return mDrawnPoints; } protected: void addPolygon(UBGraphicsPolygonItem* pol); @@ -68,11 +68,11 @@ class UBGraphicsStroke QList mPolygons; - /// Points that were drawn by the user (actually received through input device) - QList mDrawnPoints; + /// Points that were drawn by the user (i.e, actually received through input device) + QList mReceivedPoints; /// All the points (including interpolated) that are used to draw the stroke - QList mAllPoints; + QList mDrawnPoints; qreal mAntiScaleRatio; };