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
preferencesAboutTextFull
Craig Watson 9 years ago
parent 19cfe0ac1c
commit 4811d35017
  1. 96
      src/domain/UBGraphicsScene.cpp
  2. 3
      src/domain/UBGraphicsScene.h
  3. 29
      src/domain/UBGraphicsStroke.cpp
  4. 3
      src/domain/UBGraphicsStroke.h
  5. 105
      src/frameworks/UBGeometryUtils.cpp
  6. 1
      src/frameworks/UBGeometryUtils.h

@ -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<QPointF> 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<QGraphicsItem*> 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<QPointF>& 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<QGraphicsItem*> 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<QPointF>& points, qreal startWidth, qreal endWidth)
{
QPolygonF polygon = UBGeometryUtils::curveToPolygon(points, startWidth, endWidth);
return polygonToPolygonItem(polygon);
}
void UBGraphicsScene::clearSelectionFrame()
{
if (mSelectionFrame) {

@ -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<QPointF>& 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<QPointF>& points, qreal startWidth, qreal endWidth);
void addPolygonItemToCurrentStroke(UBGraphicsPolygonItem* polygonItem);
void initPolygonItem(UBGraphicsPolygonItem*);

@ -70,19 +70,6 @@ QList<QPointF> 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<QPointF>();
}
*/
if (interpolationMethod == UBInterpolator::NoInterpolation || n == 0) {
mDrawnPoints << point;
mAllPoints << point;
@ -92,14 +79,20 @@ QList<QPointF> 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<QPointF>();
}
if (n == 1) {
// We start with a straight line to the first midway point
QPointF lastPoint = mDrawnPoints[0];
@ -119,7 +112,7 @@ QList<QPointF> UBGraphicsStroke::addPoint(const QPointF& point, UBInterpolator::
bz.setPoints(startPoint, p1, endPoint);
QList<QPointF> newPoints = bz.getPoints(7);
QList<QPointF> newPoints = bz.getPoints(10);
foreach(QPointF p, newPoints) {
mAllPoints << p;
@ -127,8 +120,6 @@ QList<QPointF> UBGraphicsStroke::addPoint(const QPointF& point, UBInterpolator::
mDrawnPoints << point;
return newPoints;
}
else {
@ -149,7 +140,7 @@ QList<QPointF> 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();

@ -70,7 +70,10 @@ class UBGraphicsStroke
QList<UBGraphicsPolygonItem*> mPolygons;
/// Points that were drawn (actually received through input device)
QList<QPointF> mDrawnPoints;
/// All the points (including interpolated) that are used to draw the stroke
QList<QPointF> mAllPoints;
};

@ -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<QPointF>& points, qreal startWidth, qreal endWidth)
{
typedef QPair<QPointF, QPointF> 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<pointPair> 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())));

@ -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<QPointF>& points, qreal startWidth, qreal endWidth);
static QPointF pointConstrainedInRect(QPointF point, QRectF rect);
static QPoint pointConstrainedInRect(QPoint point, QRect rect);

Loading…
Cancel
Save