(WIP) Interpolation of strokes. Progress so far:

- 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
preferencesAboutTextFull
Craig Watson 9 years ago
parent fb6bc855e9
commit 4491341a4d
  1. 3
      OpenBoard.pro
  2. 415
      resources/forms/brushProperties.ui
  3. 2
      src/core/UBPreferencesController.cpp
  4. 2
      src/core/UBSettings.cpp
  5. 2
      src/core/UBSettings.h
  6. 88
      src/domain/UBGraphicsScene.cpp
  7. 1
      src/domain/UBGraphicsScene.h
  8. 171
      src/domain/UBGraphicsStroke.cpp
  9. 11
      src/domain/UBGraphicsStroke.h
  10. 139
      src/frameworks/UBInterpolator.cpp
  11. 96
      src/frameworks/UBInterpolator.h
  12. 6
      src/frameworks/frameworks.pri

@ -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)

@ -1,7 +1,8 @@
<ui version="4.0" >
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>brushProperties</class>
<widget class="QFrame" name="brushProperties" >
<property name="geometry" >
<widget class="QFrame" name="brushProperties">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
@ -9,25 +10,25 @@
<height>808</height>
</rect>
</property>
<property name="windowTitle" >
<property name="windowTitle">
<string/>
</property>
<property name="frameShape" >
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="frameShadow" >
<property name="frameShadow">
<enum>QFrame::Plain</enum>
</property>
<layout class="QGridLayout" name="gridLayout" >
<item row="0" column="1" >
<spacer name="nSpacer" >
<property name="orientation" >
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="1">
<spacer name="nSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType" >
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0" >
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
@ -35,53 +36,40 @@
</property>
</spacer>
</item>
<item row="1" column="0" >
<spacer name="nwSpacer" >
<property name="orientation" >
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0" >
<size>
<width>0</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
<item row="1" column="1" >
<widget class="QFrame" name="colorFrame" >
<property name="frameShape" >
<item row="1" column="1">
<widget class="QFrame" name="colorFrame">
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="frameShadow" >
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QVBoxLayout" name="verticalLayout" >
<property name="spacing" >
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>0</number>
</property>
<item>
<widget class="QFrame" name="lightBackgroundFrame" >
<property name="frameShape" >
<widget class="QFrame" name="lightBackgroundFrame">
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="frameShadow" >
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QHBoxLayout" name="lightBackgroundLayout" >
<layout class="QHBoxLayout" name="lightBackgroundLayout">
<item>
<widget class="QLabel" name="lightBackgroundLabel" >
<property name="text" >
<widget class="QLabel" name="lightBackgroundLabel">
<property name="text">
<string>On Light Background</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer" >
<property name="orientation" >
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0" >
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
@ -90,17 +78,17 @@
</spacer>
</item>
<item>
<widget class="UBColorPicker" name="lightBackgroundColorPicker0" >
<property name="minimumSize" >
<widget class="UBColorPicker" name="lightBackgroundColorPicker0">
<property name="minimumSize">
<size>
<width>32</width>
<height>32</height>
</size>
</property>
<property name="frameShape" >
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow" >
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
</widget>
@ -109,27 +97,27 @@
</widget>
</item>
<item>
<widget class="QFrame" name="darkBackgroundFrame" >
<property name="frameShape" >
<widget class="QFrame" name="darkBackgroundFrame">
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="frameShadow" >
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QHBoxLayout" name="darkBackgroundLayout" >
<layout class="QHBoxLayout" name="darkBackgroundLayout">
<item>
<widget class="QLabel" name="darkBackgroundLabel" >
<property name="text" >
<widget class="QLabel" name="darkBackgroundLabel">
<property name="text">
<string>On Dark Background</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2" >
<property name="orientation" >
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0" >
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
@ -138,17 +126,17 @@
</spacer>
</item>
<item>
<widget class="UBColorPicker" name="darkBackgroundColorPicker0" >
<property name="minimumSize" >
<widget class="UBColorPicker" name="darkBackgroundColorPicker0">
<property name="minimumSize">
<size>
<width>32</width>
<height>32</height>
</size>
</property>
<property name="frameShape" >
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow" >
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
</widget>
@ -159,34 +147,21 @@
</layout>
</widget>
</item>
<item row="1" column="2" >
<spacer name="neSpacer" >
<property name="orientation" >
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0" >
<size>
<width>0</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
<item row="2" column="0" colspan="3" >
<widget class="QFrame" name="opacityFrame" >
<property name="frameShape" >
<item row="2" column="0" colspan="3">
<widget class="QFrame" name="opacityFrame">
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="frameShadow" >
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QHBoxLayout" name="horizontalLayout" >
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<spacer name="wSpacer_2" >
<property name="orientation" >
<spacer name="wSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0" >
<property name="sizeHint" stdset="0">
<size>
<width>154</width>
<height>20</height>
@ -195,40 +170,40 @@
</spacer>
</item>
<item>
<widget class="QLabel" name="opacityLabel" >
<property name="text" >
<widget class="QLabel" name="opacityLabel">
<property name="text">
<string>Opacity</string>
</property>
</widget>
</item>
<item>
<widget class="QSlider" name="opacitySlider" >
<property name="minimum" >
<widget class="QSlider" name="opacitySlider">
<property name="minimum">
<number>20</number>
</property>
<property name="maximum" >
<property name="maximum">
<number>100</number>
</property>
<property name="value" >
<property name="value">
<number>50</number>
</property>
<property name="orientation" >
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="tickPosition" >
<property name="tickPosition">
<enum>QSlider::TicksAbove</enum>
</property>
<property name="tickInterval" >
<property name="tickInterval">
<number>20</number>
</property>
</widget>
</item>
<item>
<spacer name="eSpacer_2" >
<property name="orientation" >
<spacer name="eSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0" >
<property name="sizeHint" stdset="0">
<size>
<width>156</width>
<height>20</height>
@ -239,40 +214,27 @@
</layout>
</widget>
</item>
<item row="3" column="1" >
<spacer name="cSpacer" >
<property name="orientation" >
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0" >
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="4" column="0" colspan="3" >
<widget class="QGroupBox" name="lineWidthGroupBox" >
<property name="sizePolicy" >
<sizepolicy vsizetype="Preferred" hsizetype="Minimum" >
<item row="4" column="0" colspan="3">
<widget class="QGroupBox" name="lineWidthGroupBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title" >
<property name="title">
<string>Line Width</string>
</property>
<layout class="QGridLayout" name="gridLayout_2" >
<item row="1" column="1" >
<spacer name="wSpacer" >
<property name="orientation" >
<layout class="QGridLayout" name="gridLayout_2">
<item row="1" column="1">
<spacer name="wSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType" >
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0" >
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
@ -280,15 +242,15 @@
</property>
</spacer>
</item>
<item row="1" column="3" >
<spacer name="eSpacer" >
<property name="orientation" >
<item row="1" column="3">
<spacer name="eSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType" >
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0" >
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
@ -296,149 +258,149 @@
</property>
</spacer>
</item>
<item row="1" column="2" >
<widget class="QSlider" name="fineSlider" >
<property name="minimum" >
<item row="1" column="2">
<widget class="QSlider" name="fineSlider">
<property name="minimum">
<number>5</number>
</property>
<property name="maximum" >
<property name="maximum">
<number>500</number>
</property>
<property name="value" >
<property name="value">
<number>5</number>
</property>
<property name="orientation" >
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="tickPosition" >
<property name="tickPosition">
<enum>QSlider::TicksAbove</enum>
</property>
<property name="tickInterval" >
<property name="tickInterval">
<number>100</number>
</property>
</widget>
</item>
<item row="2" column="0" >
<widget class="QLabel" name="mediumLabel" >
<property name="text" >
<item row="2" column="0">
<widget class="QLabel" name="mediumLabel">
<property name="text">
<string>Medium</string>
</property>
</widget>
</item>
<item row="2" column="2" >
<widget class="QSlider" name="mediumSlider" >
<property name="minimum" >
<item row="2" column="2">
<widget class="QSlider" name="mediumSlider">
<property name="minimum">
<number>5</number>
</property>
<property name="maximum" >
<property name="maximum">
<number>500</number>
</property>
<property name="orientation" >
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="tickPosition" >
<property name="tickPosition">
<enum>QSlider::TicksAbove</enum>
</property>
<property name="tickInterval" >
<property name="tickInterval">
<number>100</number>
</property>
</widget>
</item>
<item row="3" column="0" >
<widget class="QLabel" name="strongLabel" >
<property name="text" >
<item row="3" column="0">
<widget class="QLabel" name="strongLabel">
<property name="text">
<string>Strong</string>
</property>
</widget>
</item>
<item row="3" column="2" >
<widget class="QSlider" name="strongSlider" >
<property name="minimum" >
<item row="3" column="2">
<widget class="QSlider" name="strongSlider">
<property name="minimum">
<number>5</number>
</property>
<property name="maximum" >
<property name="maximum">
<number>500</number>
</property>
<property name="orientation" >
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="tickPosition" >
<property name="tickPosition">
<enum>QSlider::TicksAbove</enum>
</property>
<property name="tickInterval" >
<property name="tickInterval">
<number>100</number>
</property>
</widget>
</item>
<item row="1" column="4" >
<widget class="UBCircleFrame" name="fineDisplayFrame" >
<property name="sizePolicy" >
<sizepolicy vsizetype="Fixed" hsizetype="Fixed" >
<item row="1" column="4">
<widget class="UBCircleFrame" name="fineDisplayFrame">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>60</horstretch>
<verstretch>60</verstretch>
</sizepolicy>
</property>
<property name="minimumSize" >
<property name="minimumSize">
<size>
<width>60</width>
<height>60</height>
</size>
</property>
<property name="frameShape" >
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow" >
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
</widget>
</item>
<item row="2" column="4" >
<widget class="UBCircleFrame" name="mediumDisplayFrame" >
<property name="sizePolicy" >
<sizepolicy vsizetype="Fixed" hsizetype="Fixed" >
<item row="2" column="4">
<widget class="UBCircleFrame" name="mediumDisplayFrame">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>60</horstretch>
<verstretch>60</verstretch>
</sizepolicy>
</property>
<property name="minimumSize" >
<property name="minimumSize">
<size>
<width>60</width>
<height>60</height>
</size>
</property>
<property name="frameShape" >
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow" >
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
</widget>
</item>
<item row="3" column="4" >
<widget class="UBCircleFrame" name="strongDisplayFrame" >
<property name="sizePolicy" >
<sizepolicy vsizetype="Fixed" hsizetype="Fixed" >
<item row="3" column="4">
<widget class="UBCircleFrame" name="strongDisplayFrame">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>60</horstretch>
<verstretch>60</verstretch>
</sizepolicy>
</property>
<property name="minimumSize" >
<property name="minimumSize">
<size>
<width>60</width>
<height>60</height>
</size>
</property>
<property name="frameShape" >
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow" >
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
</widget>
</item>
<item row="1" column="0" >
<widget class="QLabel" name="fineLabel" >
<property name="text" >
<item row="1" column="0">
<widget class="QLabel" name="fineLabel">
<property name="text">
<string>Fine</string>
</property>
</widget>
@ -446,50 +408,85 @@
</layout>
</widget>
</item>
<item row="5" column="0" colspan="3" >
<widget class="QFrame" name="pressureFrame" >
<property name="frameShape" >
<item row="3" column="1">
<spacer name="cSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="1" column="0">
<spacer name="nwSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
<item row="1" column="2">
<spacer name="neSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
<item row="5" column="0" colspan="3">
<widget class="QFrame" name="pressureFrame">
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="frameShadow" >
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_4" >
<property name="margin" >
<layout class="QHBoxLayout" name="horizontalLayout_4">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<spacer name="swSpacer" >
<property name="orientation" >
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0" >
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QCheckBox" name="pressureSensitiveCheckBox" >
<property name="sizePolicy" >
<sizepolicy vsizetype="Fixed" hsizetype="MinimumExpanding" >
<widget class="QCheckBox" name="pressureSensitiveCheckBox">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text" >
<property name="text">
<string>Pen is Pressure Sensitive</string>
</property>
</widget>
</item>
<item>
<spacer name="seSpacer" >
<property name="orientation" >
<spacer name="seSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0" >
<property name="sizeHint" stdset="0">
<size>
<width>198</width>
<height>20</height>
@ -500,6 +497,30 @@
</layout>
</widget>
</item>
<item row="7" column="0" colspan="2">
<layout class="QHBoxLayout" name="interpolationFrame">
<item>
<widget class="QCheckBox" name="interpolateStrokesCheckBox">
<property name="text">
<string>Smooth strokes (experimental)</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</widget>
<customwidgets>

@ -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<QColor> 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);

@ -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();

@ -269,6 +269,8 @@ class UBSettings : public QObject
UBSetting* boardUseHighResTabletEvent;
UBSetting* boardInterpolatePenStrokes;
UBSetting* boardKeyboardPaletteKeyBtnSize;
UBSetting* appStartMode;

@ -23,6 +23,7 @@
*/
#include <unistd.h>
#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<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);
}
}
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;
}
}

@ -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);

@ -31,6 +31,7 @@
#include "core/memcheck.h"
UBGraphicsStroke::UBGraphicsStroke()
{
// NOOP
@ -61,6 +62,129 @@ QList<UBGraphicsPolygonItem*> 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<QPointF> 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<QPointF>();
}
*/
if (interpolationMethod == UBInterpolator::NoInterpolation || n == 0) {
mDrawnPoints << point;
mAllPoints << point;
return QList<QPointF>() << 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<QPointF>();
if (n == 1) {
// We start with a straight line to the first midway point
QPointF lastPoint = mDrawnPoints[0];
mDrawnPoints << point;
return QList<QPointF>() << ((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<QPointF> 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<QPointF> 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<QPointF>() << 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<QPointF>() << 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;
}

@ -31,6 +31,8 @@
#include <QtGui>
#include "core/UB.h"
#include "frameworks/UBInterpolator.h"
class UBGraphicsPolygonItem;
@ -55,6 +57,12 @@ class UBGraphicsStroke
void clear();
QList<QPointF> addPoint(const QPointF& point, UBInterpolator::InterpolationMethod interpolationMethod = UBInterpolator::NoInterpolation);
UBGraphicsStroke* smoothe();
const QList<QPointF>& points() { return mAllPoints; }
protected:
void addPolygon(UBGraphicsPolygonItem* pol);
@ -62,6 +70,9 @@ class UBGraphicsStroke
QList<UBGraphicsPolygonItem*> mPolygons;
QList<QPointF> mDrawnPoints;
QList<QPointF> mAllPoints;
};
#endif /* UBGRAPHICSSTROKE_H_ */

@ -0,0 +1,139 @@
#include "UBInterpolator.h"
UBInterpolator::UBInterpolator()
{
}
UBInterpolator::~UBInterpolator()
{
}
UBSimpleSpline::UBSimpleSpline()
{
}
void UBSimpleSpline::setPoints(QList<QPointF> 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<QPointF> 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<QPointF> 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<QPointF> UBQuadraticBezier::getPoints(int n)
{
QList<QPointF> 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;
}

@ -0,0 +1,96 @@
#ifndef UBINTERPOLATOR_H
#define UBINTERPOLATOR_H
#include <QtGui>
#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<QPointF> 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<QPointF> 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<QPointF> points);
virtual double y(double x);
private:
alglib::spline1dinterpolant * mInterpolant;
};
class UBQuadraticBezier : public UBInterpolator
{
public:
UBQuadraticBezier();
virtual ~UBQuadraticBezier();
virtual void setPoints(QList<QPointF> points);
void setPoints(QPointF start, QPointF control, QPointF end);
//virtual double y(double x);
QList<QPointF> getPoints(int n);
private:
QPainterPath* mPath;
};
#endif // UBINTERPOLATOR_H

@ -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 {

Loading…
Cancel
Save