diff --git a/resources/forms/brushProperties.ui b/resources/forms/brushProperties.ui
index eea50fba..2866714e 100644
--- a/resources/forms/brushProperties.ui
+++ b/resources/forms/brushProperties.ui
@@ -521,6 +521,30 @@
+ -
+
+
-
+
+
+ Simplify strokes after drawing (experimental)
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+
+
diff --git a/src/core/UBPreferencesController.cpp b/src/core/UBPreferencesController.cpp
index 7f87cdda..df58462c 100644
--- a/src/core/UBPreferencesController.cpp
+++ b/src/core/UBPreferencesController.cpp
@@ -166,6 +166,7 @@ void UBPreferencesController::wire()
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)));
+ connect(mPenProperties->simplifyStrokesCheckBox, SIGNAL(clicked(bool)), settings->boardSimplifyPenStrokes, SLOT(setBool(bool)));
// marker
QList markerLightBackgroundColors = settings->boardMarkerLightBackgroundColors->colors();
@@ -183,6 +184,8 @@ void UBPreferencesController::wire()
connect(mMarkerProperties->mediumSlider, SIGNAL(valueChanged(int)), this, SLOT(widthSliderChanged(int)));
connect(mMarkerProperties->strongSlider, SIGNAL(valueChanged(int)), this, SLOT(widthSliderChanged(int)));
connect(mMarkerProperties->pressureSensitiveCheckBox, SIGNAL(clicked(bool)), settings, SLOT(setMarkerPressureSensitive(bool)));
+ connect(mMarkerProperties->interpolateStrokesCheckBox, SIGNAL(clicked(bool)), settings->boardInterpolateMarkerStrokes, SLOT(setBool(bool)));
+ connect(mMarkerProperties->simplifyStrokesCheckBox, SIGNAL(clicked(bool)), settings->boardSimplifyMarkerStrokes, SLOT(setBool(bool)));
connect(mMarkerProperties->opacitySlider, SIGNAL(valueChanged(int)), this, SLOT(opacitySliderChanged(int)));
// about tab
@@ -233,12 +236,15 @@ void UBPreferencesController::init()
mPenProperties->strongSlider->setValue(settings->boardPenStrongWidth->get().toDouble() * sSliderRatio);
mPenProperties->pressureSensitiveCheckBox->setChecked(settings->boardPenPressureSensitive->get().toBool());
mPenProperties->interpolateStrokesCheckBox->setChecked(settings->boardInterpolatePenStrokes->get().toBool());
+ mPenProperties->simplifyStrokesCheckBox->setChecked(settings->boardSimplifyPenStrokes->get().toBool());
// marker tab
mMarkerProperties->fineSlider->setValue(settings->boardMarkerFineWidth->get().toDouble() * sSliderRatio);
mMarkerProperties->mediumSlider->setValue(settings->boardMarkerMediumWidth->get().toDouble() * sSliderRatio);
mMarkerProperties->strongSlider->setValue(settings->boardMarkerStrongWidth->get().toDouble() * sSliderRatio);
mMarkerProperties->pressureSensitiveCheckBox->setChecked(settings->boardMarkerPressureSensitive->get().toBool());
+ mMarkerProperties->interpolateStrokesCheckBox->setChecked(settings->boardInterpolateMarkerStrokes->get().toBool());
+ mMarkerProperties->simplifyStrokesCheckBox->setChecked(settings->boardSimplifyMarkerStrokes->get().toBool());
mMarkerProperties->opacitySlider->setValue(settings->boardMarkerAlpha->get().toDouble() * 100);
diff --git a/src/core/UBSettings.cpp b/src/core/UBSettings.cpp
index fc9e2b55..cfde5119 100644
--- a/src/core/UBSettings.cpp
+++ b/src/core/UBSettings.cpp
@@ -270,6 +270,11 @@ void UBSettings::init()
boardUseHighResTabletEvent = new UBSetting(this, "Board", "UseHighResTabletEvent", true);
boardInterpolatePenStrokes = new UBSetting(this, "Board", "InterpolatePenStrokes", true);
+ boardSimplifyPenStrokes = new UBSetting(this, "Board", "SimplifyPenStrokes", true);
+ boardSimplifyPenStrokesThresholdAngle = new UBSetting(this, "Board", "SimplifyPenStrokesThresholdAngle", 2);
+
+ boardInterpolateMarkerStrokes = new UBSetting(this, "Board", "InterpolateMarkerStrokes", true);
+ boardSimplifyMarkerStrokes = new UBSetting(this, "Board", "SimplifyMarkerStrokes", true);
boardKeyboardPaletteKeyBtnSize = new UBSetting(this, "Board", "KeyboardPaletteKeyBtnSize", "16x16");
ValidateKeyboardPaletteKeyBtnSize();
diff --git a/src/core/UBSettings.h b/src/core/UBSettings.h
index 6be12962..0fb8bf68 100644
--- a/src/core/UBSettings.h
+++ b/src/core/UBSettings.h
@@ -274,6 +274,10 @@ class UBSettings : public QObject
UBSetting* boardUseHighResTabletEvent;
UBSetting* boardInterpolatePenStrokes;
+ UBSetting* boardSimplifyPenStrokes;
+ UBSetting* boardSimplifyPenStrokesThresholdAngle;
+ UBSetting* boardInterpolateMarkerStrokes;
+ UBSetting* boardSimplifyMarkerStrokes;
UBSetting* boardKeyboardPaletteKeyBtnSize;
diff --git a/src/domain/UBGraphicsPolygonItem.cpp b/src/domain/UBGraphicsPolygonItem.cpp
index 39fb5151..320da3f3 100644
--- a/src/domain/UBGraphicsPolygonItem.cpp
+++ b/src/domain/UBGraphicsPolygonItem.cpp
@@ -121,10 +121,12 @@ void UBGraphicsPolygonItem::setStrokesGroup(UBGraphicsStrokesGroup *group)
void UBGraphicsPolygonItem::setStroke(UBGraphicsStroke* stroke)
{
- clearStroke();
+ if (stroke) {
+ clearStroke();
- mStroke = stroke;
- mStroke->addPolygon(this);
+ mStroke = stroke;
+ mStroke->addPolygon(this);
+ }
}
UBGraphicsStroke* UBGraphicsPolygonItem::stroke() const
diff --git a/src/domain/UBGraphicsScene.cpp b/src/domain/UBGraphicsScene.cpp
index 23db81d5..b63ee57f 100644
--- a/src/domain/UBGraphicsScene.cpp
+++ b/src/domain/UBGraphicsScene.cpp
@@ -415,7 +415,7 @@ bool UBGraphicsScene::inputDevicePress(const QPointF& scenePos, const qreal& pre
// ---------------------------------------------------------------
// Create a new Stroke. A Stroke is a collection of QGraphicsLines
// ---------------------------------------------------------------
- mCurrentStroke = new UBGraphicsStroke();
+ mCurrentStroke = new UBGraphicsStroke(this);
if (currentTool != UBStylusTool::Line){
// Handle the pressure
@@ -438,7 +438,7 @@ bool UBGraphicsScene::inputDevicePress(const QPointF& scenePos, const qreal& pre
moveTo(scenePos);
drawLineTo(scenePos, width, UBDrawingController::drawingController()->stylusTool() == UBStylusTool::Line);
- mCurrentStroke->addPoint(scenePos);
+ mCurrentStroke->addPoint(scenePos, width);
}
accepted = true;
}
@@ -543,7 +543,7 @@ bool UBGraphicsScene::inputDeviceMove(const QPointF& scenePos, const qreal& pres
}
if (!mCurrentStroke)
- mCurrentStroke = new UBGraphicsStroke();
+ mCurrentStroke = new UBGraphicsStroke(this);
if(dc->mActiveRuler){
dc->mActiveRuler->DrawLine(position, width);
@@ -551,20 +551,15 @@ bool UBGraphicsScene::inputDeviceMove(const QPointF& scenePos, const qreal& pres
else{
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;
- }
- */
-
- if (UBSettings::settings()->boardInterpolatePenStrokes->get().toBool()) {
+ if ((currentTool == UBStylusTool::Pen && UBSettings::settings()->boardInterpolatePenStrokes->get().toBool())
+ || (currentTool == UBStylusTool::Marker && UBSettings::settings()->boardInterpolateMarkerStrokes->get().toBool()))
+ {
interpolator = UBInterpolator::Bezier;
}
- QList newPoints = mCurrentStroke->addPoint(scenePos, interpolator);
+ QList > newPoints = mCurrentStroke->addPoint(scenePos, width, interpolator);
if (newPoints.length() > 1) {
- drawCurve(newPoints, mPreviousWidth, width);
+ drawCurve(newPoints);
}
if (interpolator == UBInterpolator::Bezier) {
@@ -578,7 +573,7 @@ bool UBGraphicsScene::inputDeviceMove(const QPointF& scenePos, const qreal& pres
mTempPolygon = NULL;
}
- QPointF lastDrawnPoint = newPoints.last();
+ QPointF lastDrawnPoint = newPoints.last().first;
mTempPolygon = lineToPolygonItem(QLineF(lastDrawnPoint, scenePos), mPreviousWidth, width);
addItem(mTempPolygon);
@@ -658,6 +653,14 @@ bool UBGraphicsScene::inputDeviceRelease()
addPolygonItemToCurrentStroke(poly);
}
+ // replace the stroke by a simplified version of it
+ if ((currentTool == UBStylusTool::Pen && UBSettings::settings()->boardSimplifyPenStrokes->get().toBool())
+ || (currentTool == UBStylusTool::Marker && UBSettings::settings()->boardSimplifyMarkerStrokes->get().toBool()))
+ {
+ simplifyCurrentStroke();
+ }
+
+
UBGraphicsStrokesGroup* pStrokes = new UBGraphicsStrokesGroup();
// Remove the strokes that were just drawn here and replace them by a stroke item
@@ -857,6 +860,15 @@ void UBGraphicsScene::drawLineTo(const QPointF &pEndPoint, const qreal &startWid
}
}
+void UBGraphicsScene::drawCurve(const QList >& points)
+{
+ UBGraphicsPolygonItem* polygonItem = curveToPolygonItem(points);
+ addPolygonItemToCurrentStroke(polygonItem);
+
+ mPreviousPoint = points.last().first;
+ mPreviousWidth = points.last().second;
+}
+
void UBGraphicsScene::drawCurve(const QList& points, qreal startWidth, qreal endWidth)
{
UBGraphicsPolygonItem* polygonItem = curveToPolygonItem(points, startWidth, endWidth);
@@ -886,7 +898,7 @@ void UBGraphicsScene::addPolygonItemToCurrentStroke(UBGraphicsPolygonItem* polyg
// Here we add the item to the scene
addItem(polygonItem);
if (!mCurrentStroke)
- mCurrentStroke = new UBGraphicsStroke();
+ mCurrentStroke = new UBGraphicsStroke(this);
polygonItem->setStroke(mCurrentStroke);
@@ -1151,12 +1163,19 @@ UBGraphicsPolygonItem* UBGraphicsScene::arcToPolygonItem(const QLineF& pStartRad
return polygonToPolygonItem(polygon);
}
+UBGraphicsPolygonItem* UBGraphicsScene::curveToPolygonItem(const QList >& points)
+{
+ QPolygonF polygon = UBGeometryUtils::curveToPolygon(points, false, true);
+
+ 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()
@@ -2625,6 +2644,30 @@ bool UBGraphicsScene::hasTextItemWithFocus(UBGraphicsGroupContainerItem *item){
return bHasFocus;
}
+
+void UBGraphicsScene::simplifyCurrentStroke()
+{
+ if (!mCurrentStroke)
+ return;
+
+ UBGraphicsStroke* simplerStroke = mCurrentStroke->simplify();
+ if (!simplerStroke)
+ return;
+
+ foreach(UBGraphicsPolygonItem* poly, mCurrentStroke->polygons()){
+ mPreviousPolygonItems.removeAll(poly);
+ removeItem(poly);
+ }
+
+ mCurrentStroke = simplerStroke;
+
+ foreach(UBGraphicsPolygonItem* poly, mCurrentStroke->polygons()) {
+ addItem(poly);
+ mPreviousPolygonItems.append(poly);
+ }
+
+}
+
void UBGraphicsScene::setDocumentUpdated()
{
if (document())
@@ -2738,5 +2781,5 @@ void UBGraphicsScene::setToolCursor(int tool)
void UBGraphicsScene::initStroke()
{
- mCurrentStroke = new UBGraphicsStroke();
+ mCurrentStroke = new UBGraphicsStroke(this);
}
diff --git a/src/domain/UBGraphicsScene.h b/src/domain/UBGraphicsScene.h
index 37f1a470..74577250 100644
--- a/src/domain/UBGraphicsScene.h
+++ b/src/domain/UBGraphicsScene.h
@@ -196,6 +196,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);
void drawCurve(const QList& points, qreal startWidth, qreal endWidth);
bool isEmpty() const;
@@ -368,7 +369,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);
+ UBGraphicsPolygonItem* curveToPolygonItem(const QList > &points);
+ UBGraphicsPolygonItem* curveToPolygonItem(const QList &points, qreal startWidth, qreal endWidth);
void addPolygonItemToCurrentStroke(UBGraphicsPolygonItem* polygonItem);
void initPolygonItem(UBGraphicsPolygonItem*);
@@ -402,6 +404,7 @@ public slots:
void updateEraserColor();
void updateMarkerCircleColor();
bool hasTextItemWithFocus(UBGraphicsGroupContainerItem* item);
+ void simplifyCurrentStroke();
QGraphicsEllipseItem* mEraser;
QGraphicsEllipseItem* mPointer; // "laser" pointer
diff --git a/src/domain/UBGraphicsStroke.cpp b/src/domain/UBGraphicsStroke.cpp
index f20b680b..a640d0a0 100644
--- a/src/domain/UBGraphicsStroke.cpp
+++ b/src/domain/UBGraphicsStroke.cpp
@@ -34,9 +34,15 @@
#include "board/UBBoardController.h"
#include "core/UBApplication.h"
#include "core/memcheck.h"
+#include "domain/UBGraphicsScene.h"
+#include "frameworks/UBGeometryUtils.h"
-UBGraphicsStroke::UBGraphicsStroke()
+
+typedef QPair strokePoint;
+
+UBGraphicsStroke::UBGraphicsStroke(UBGraphicsScene *scene)
+ :mScene(scene)
{
mAntiScaleRatio = 1./(UBApplication::boardController->systemScaleFactor() * UBApplication::boardController->currentZoom());
}
@@ -44,7 +50,10 @@ UBGraphicsStroke::UBGraphicsStroke()
UBGraphicsStroke::~UBGraphicsStroke()
{
- // NOOP
+ foreach(UBGraphicsPolygonItem* poly, mPolygons)
+ poly->setStroke(NULL);
+
+ mPolygons.clear();
}
@@ -70,47 +79,51 @@ QList UBGraphicsStroke::polygons() const
* @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)
+QList > UBGraphicsStroke::addPoint(const QPointF& point, qreal width, UBInterpolator::InterpolationMethod interpolationMethod)
{
+ strokePoint newPoint(point, width);
+
int n = mReceivedPoints.size();
if (n == 0) {
- mReceivedPoints << point;
- mDrawnPoints << point;
- return QList();
+ mReceivedPoints << newPoint;
+ mDrawnPoints << newPoint;
+ return QList();
}
if (interpolationMethod == UBInterpolator::NoInterpolation) {
- QPointF lastPoint = mReceivedPoints.last();
- mReceivedPoints << point;
- mDrawnPoints << point;
- return QList() << lastPoint << point;
+ strokePoint lastPoint = mReceivedPoints.last();
+ mReceivedPoints << newPoint;
+ mDrawnPoints << newPoint;
+ return QList() << lastPoint << newPoint;
}
else if (interpolationMethod == UBInterpolator::Bezier) {
- // This is a bit special, as the curve we are interpolating is not between two drawn points;
+ // 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.
// 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 = 5*mAntiScaleRatio;
- qreal distance = QLineF(mReceivedPoints.last(), point).length();
+ // (less polygons to draw) but mostly with keeping the curve smooth.
+ qreal MIN_DISTANCE = 3*mAntiScaleRatio;
+ qreal distance = QLineF(mReceivedPoints.last().first, newPoint.first).length();
if (distance < MIN_DISTANCE) {
- return QList() << mDrawnPoints.last();
+ return QList() << mDrawnPoints.last();
}
// The first segment is just a straight line to the first midway point
if (n == 1) {
- QPointF lastPoint = mReceivedPoints[0];
- mReceivedPoints << point;
- mDrawnPoints << QPointF((lastPoint + point)/2.0);
+ QPointF lastPoint = mReceivedPoints[0].first;
+ qreal lastWidth = mReceivedPoints[0].second;
+ strokePoint p(((lastPoint+point)/2.0), (lastWidth+width)/2.0);
+ mReceivedPoints << newPoint;
+ mDrawnPoints << p;
- return QList() << lastPoint << ((lastPoint + point)/2.0);
+ return QList() << mReceivedPoints[0] << p;
}
- QPointF p0 = mReceivedPoints[mReceivedPoints.size() - 2];
- QPointF p1 = mReceivedPoints[mReceivedPoints.size() - 1];
+ QPointF p0 = mReceivedPoints[mReceivedPoints.size() - 2].first;
+ QPointF p1 = mReceivedPoints[mReceivedPoints.size() - 1].first;
QPointF p2 = point;
UBQuadraticBezier bz;
@@ -120,21 +133,28 @@ QList UBGraphicsStroke::addPoint(const QPointF& point, UBInterpolator::
bz.setPoints(startPoint, p1, endPoint);
- QList newPoints = bz.getPoints(10);
+ QList calculated = bz.getPoints(10);
+ QList newPoints;
+
+ qreal startWidth = mDrawnPoints.last().second;
+
+ for (int i(0); i < calculated.size(); ++i) {
+ qreal w = startWidth + (qreal(i)/qreal(calculated.size()-1)) * (width - startWidth);
+ newPoints << strokePoint(calculated[i], w);
+ }
// avoid adding duplicates
- if (newPoints.first() == mDrawnPoints.last())
+ if (newPoints.first().first == mDrawnPoints.last().first)
mDrawnPoints.removeLast();
- foreach(QPointF p, newPoints) {
+ foreach(strokePoint p, newPoints)
mDrawnPoints << p;
- }
- mReceivedPoints << point;
+ mReceivedPoints << strokePoint(point, width);
return newPoints;
}
- return QList();
+ return QList();
}
bool UBGraphicsStroke::hasPressure()
@@ -179,3 +199,124 @@ void UBGraphicsStroke::clear()
mPolygons.clear();
}
}
+
+/**
+ * @brief Return a simplified version of the stroke, with less points and polygons.
+ *
+ */
+UBGraphicsStroke* UBGraphicsStroke::simplify()
+{
+ if (mDrawnPoints.size() < 3)
+ return NULL;
+
+ UBGraphicsStroke* newStroke = new UBGraphicsStroke();
+ newStroke->mDrawnPoints = QList(mDrawnPoints);
+
+ QList& points = newStroke->mDrawnPoints;
+ qDebug() << "Simplifying. Before: " << points.size() << " points and " << polygons().size() << " polygons";
+
+ /* Basic simplifying algorithm: consider A, B and C the current point and the two following ones.
+ * If the angle between (AB) and (BC) is lower than a certain threshold,
+ * the three points are considered to be aligned and the middle one (B) is removed.
+ *
+ * We then consider the two following points as the new B and C while keeping the same A, and
+ * test these three points. As long as they are aligned, B is erased and we start over.
+ * If not, the current B becomes the new A, and so on.
+ *
+ *
+ * TODO: more advanced algorithm that could also simplify curved sections of the stroke
+ */
+
+ // angle difference in degrees between AB and BC below which the segments are considered colinear
+ qreal threshold = UBSettings::settings()->boardSimplifyPenStrokesThresholdAngle->get().toReal();
+
+ QList::iterator it = points.begin();
+ QList::iterator> toDelete;
+
+ while (it+2 != points.end()) {
+ QList::iterator b_it(it+1);
+
+ while (b_it+1 != points.end()) {
+ qreal angle = qFabs(QLineF(it->first, b_it->first).angle() - QLineF(b_it->first, (b_it+1)->first).angle());
+
+ if (angle < threshold)
+ b_it = points.erase(b_it);
+ else
+ break;
+ }
+
+ if (b_it+1 == points.end())
+ break;
+ else
+ it = b_it;
+ }
+
+ // Next, we iterate over the new points to build the polygons that make up the stroke
+
+ QList newPolygons;
+ QList newStrokePoints;
+ int i(0);
+
+ while (i < points.size()) {
+ bool drawCurve = false;
+
+ newStrokePoints << points[i];
+
+ // When a polygon is transparent and it overlaps with itself, it is *sometimes* filled incorrectly.
+ // Limiting the size of the polygons, and creating new ones when the angle between consecutive points is above a
+ // certain threshold helps mitigate this issue.
+ // TODO: fix fill issue
+
+ if (newStrokePoints.size() > 1 && i < points.size() - 1) {
+ qreal angle = qFabs(UBGeometryUtils::angle(points[i-1].first, points[i].first, points[i+1].first));
+ qDebug() << "Angle: " << angle;
+ if (angle > 40 && angle < 320)
+ drawCurve = true;
+ }
+
+ if (newStrokePoints.size() % 20 == 0)
+ drawCurve = true;
+
+
+ if (drawCurve) {
+ UBGraphicsPolygonItem* poly = mScene->polygonToPolygonItem(UBGeometryUtils::curveToPolygon(newStrokePoints, true, true));
+ //poly->setColor(QColor(rand()%256, rand()%256, rand()%256, poly->brush().color().alpha()));
+
+ // Subtract overlapping polygons if the stroke is translucent
+
+ if (!poly->brush().isOpaque()) {
+ foreach(UBGraphicsPolygonItem* prev, newPolygons)
+ poly->subtract(prev);
+ }
+
+ newPolygons << poly;
+ newStrokePoints.clear();
+ --i;
+ }
+
+ ++i;
+ }
+
+ if (newStrokePoints.size() > 0) {
+ UBGraphicsPolygonItem* poly = mScene->polygonToPolygonItem(UBGeometryUtils::curveToPolygon(newStrokePoints, true, true));
+
+ if (!poly->brush().isOpaque()) {
+ foreach(UBGraphicsPolygonItem* prev, newPolygons)
+ poly->subtract(prev);
+ }
+
+ newPolygons << poly;
+ }
+
+
+ newStroke->mPolygons = QList(newPolygons);
+
+ foreach(UBGraphicsPolygonItem* poly, newStroke->mPolygons) {
+ poly->setFillRule(Qt::WindingFill);
+ poly->setStroke(newStroke);
+ }
+
+ qDebug() << "After: " << points.size() << " points and " << newStroke->polygons().size() << " polygons";
+
+ return newStroke;
+}
diff --git a/src/domain/UBGraphicsStroke.h b/src/domain/UBGraphicsStroke.h
index 8c332498..918ec894 100644
--- a/src/domain/UBGraphicsStroke.h
+++ b/src/domain/UBGraphicsStroke.h
@@ -38,13 +38,14 @@
class UBGraphicsPolygonItem;
+class UBGraphicsScene;
class UBGraphicsStroke
{
friend class UBGraphicsPolygonItem;
public:
- UBGraphicsStroke();
+ UBGraphicsStroke(UBGraphicsScene* scene = NULL);
virtual ~UBGraphicsStroke();
bool hasPressure();
@@ -59,22 +60,26 @@ class UBGraphicsStroke
void clear();
- QList addPoint(const QPointF& point, UBInterpolator::InterpolationMethod interpolationMethod = UBInterpolator::NoInterpolation);
+ QList > addPoint(const QPointF& point, qreal width, UBInterpolator::InterpolationMethod interpolationMethod = UBInterpolator::NoInterpolation);
- const QList& points() { return mDrawnPoints; }
+ const QList >& points() { return mDrawnPoints; }
+
+ UBGraphicsStroke* simplify();
protected:
void addPolygon(UBGraphicsPolygonItem* pol);
private:
+ UBGraphicsScene * mScene;
+
QList mPolygons;
/// Points that were drawn by the user (i.e, actually received through input device)
- QList mReceivedPoints;
+ QList > mReceivedPoints;
/// All the points (including interpolated) that are used to draw the stroke
- QList mDrawnPoints;
+ QList > mDrawnPoints;
qreal mAntiScaleRatio;
};
diff --git a/src/frameworks/UBGeometryUtils.cpp b/src/frameworks/UBGeometryUtils.cpp
index f42f6684..eb36a27e 100644
--- a/src/frameworks/UBGeometryUtils.cpp
+++ b/src/frameworks/UBGeometryUtils.cpp
@@ -265,6 +265,36 @@ QPolygonF UBGeometryUtils::curveToPolygon(const QList& points, qreal st
if (n_points == 2)
return lineToPolygon(points[0], points[1], startWidth, endWidth);
+ QList > pointsAndWidths;
+ for (int i(0); i < n_points; ++i) {
+ qreal width = startWidth + (qreal(i)/qreal(n_points-1)) * (endWidth - startWidth);
+
+ pointsAndWidths << QPair(points[i], width);
+ }
+
+ return curveToPolygon(pointsAndWidths, true, true);
+}
+
+/**
+ * @brief Build and return a polygon from a list of points and thicknesses (at least 2)
+ *
+ * The resulting polygon will pass by all points in the curve; the segments are joined by
+ * (approximately) curved joints. The ends of the polygon can be terminated by arcs by passing
+ * `true` as the `roundStart` and/or `roundEnd` parameters.
+ *
+ */
+QPolygonF UBGeometryUtils::curveToPolygon(const QList >& points, bool roundStart, bool roundEnd)
+{
+ int n_points = points.size();
+
+ if (n_points == 0)
+ return QPolygonF();
+ if (n_points == 1)
+ return lineToPolygon(points.first().first, points.first().first, points.first().second, points.first().second);
+
+ qreal startWidth = points.first().second;
+ qreal endWidth = points.last().second;
+
/* The vertices (x's) are calculated based on the stroke's width and angle, and the position of the
supplied points (o's):
@@ -276,15 +306,16 @@ QPolygonF UBGeometryUtils::curveToPolygon(const QList& points, qreal st
The vertices above and below each 'o' point are temporarily stored together,
as a pair of points.
-
*/
+
+ typedef QPair pointPair;
QList newPoints;
- QLineF firstSegment = QLineF(points[0], points[1]);
+ QLineF firstSegment = QLineF(points[0].first, points[1].first);
QLineF normal = firstSegment.normalVector();
normal.setLength(startWidth/2.0);
- newPoints << pointPair(normal.p2(), points[0] - QPointF(normal.dx(), normal.dy()));
+ newPoints << pointPair(normal.p2(), points[0].first - QPointF(normal.dx(), normal.dy()));
/*
Calculating the vertices (d1 and d2, below) is a little less trivial for the
@@ -306,45 +337,50 @@ QPolygonF UBGeometryUtils::curveToPolygon(const QList& points, qreal st
*/
for (int i(1); i < n_points-1; ++i) {
- qreal width = startWidth + (qreal(i)/qreal(n_points-1)) * (endWidth - startWidth);
+ //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());
+ QLineF normal = (QLineF(points[i-1].first, points[i+1].first)).normalVector();
+ normal.setLength(points[i].second/2.0);
+ QPointF d1 = points[i].first + QPointF(normal.dx(), normal.dy());
+ QPointF d2 = points[i].first - 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]);
+ QLineF lastSegment = QLineF(points[n_points-2].first, points[n_points-1].first);
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());
+ QPointF d1 = points.last().first + QPointF(normal.dx(), normal.dy());
+ QPointF d2 = points.last().first - QPointF(normal.dx(), normal.dy());
newPoints << pointPair(d1, d2);
QPainterPath path;
+ path.setFillRule(Qt::WindingFill);
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);
+ if (roundEnd)
+ path.arcTo(points.last().first.x() - endWidth/2.0, points.last().first.y() - endWidth/2.0, endWidth, endWidth, (90.0 + lastSegment.angle()), -180.0);
+ else
+ 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);
+ if (roundStart)
+ path.arcTo(points[0].first.x() - startWidth/2.0, points[0].first.y() - startWidth/2.0, startWidth, startWidth, (firstSegment.angle() - 90.0), -180.0);
+ else
+ path.lineTo(newPoints[0].first);
- path.closeSubpath();
+ //path.closeSubpath();
return path.toFillPolygon();
}
@@ -391,3 +427,11 @@ void UBGeometryUtils::crashPointList(QVector &points)
}
}
}
+
+/**
+ * @brief Return the angle between three points
+ */
+qreal UBGeometryUtils::angle(const QPointF& a, const QPointF& b, const QPointF& c)
+{
+ return (QLineF(a, b).angle() - QLineF(b, c).angle());
+}
diff --git a/src/frameworks/UBGeometryUtils.h b/src/frameworks/UBGeometryUtils.h
index 6d0aeff1..988b4a20 100644
--- a/src/frameworks/UBGeometryUtils.h
+++ b/src/frameworks/UBGeometryUtils.h
@@ -48,12 +48,15 @@ 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 QPolygonF curveToPolygon(const QList >& points, bool roundStart, bool roundEnd);
static QPointF pointConstrainedInRect(QPointF point, QRectF rect);
static QPoint pointConstrainedInRect(QPoint point, QRect rect);
static void crashPointList(QVector &points);
+ static qreal angle(const QPointF& a, const QPointF& b, const QPointF& c);
+
const static int centimeterGraduationHeight;
const static int halfCentimeterGraduationHeight;
const static int millimeterGraduationHeight;
diff --git a/src/frameworks/UBInterpolator.cpp b/src/frameworks/UBInterpolator.cpp
index b5777bcd..3b75da12 100644
--- a/src/frameworks/UBInterpolator.cpp
+++ b/src/frameworks/UBInterpolator.cpp
@@ -34,7 +34,7 @@ void UBQuadraticBezier::setPoints(QPointF start, QPointF control, QPointF 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
+ * The higher n, the more accurate the resulting curve will be.
*/
QList UBQuadraticBezier::getPoints(int n)
{