Есть ли способ нарисовать плавную линию через множество точек в QT?
Количество и положение точек устанавливается во время выполнения.
В настоящее время я рисую QPainterPath, который содержит lineTo, идущий от точки к точке, создающий путь. Я использую сглаживание рендера, но путь все еще неровный.
Я видел QSplineSeries, который, кажется, дает этот вид изогнутого пути, но он не доступен в Qt4.8, которая является версией QT, которую я использую.
Другой вариант, который часто предлагается, — это использование кривых Безье, но в них используются одна начальная и конечная точка и две контрольные точки, поэтому мне нужно будет рассчитать его для каждого сегмента (каждой линии) и каким-то образом рассчитать те контрольные точки, которых у меня нет в данный момент.
В конце я реализовал некоторый обходной путь, который в основном берет две соединенные линии, удаляет точку соединения между ними и заменяет ее кривой. Поскольку у меня много маленьких линий, где такое изменение не было бы видно, я удаляю все очень короткие линии и снова соединяю открытые концы. Эта функция была в основном предоставлена Bojan Kverh, ознакомьтесь с его руководством: https://www.toptal.com/c-plus-plus/rounded-corners-bezier-curves-qpainter
А вот и функции:
namespace
{
float distance(const QPointF& pt1, const QPointF& pt2)
{
float hd = (pt1.x() - pt2.x()) * (pt1.x() - pt2.x());
float vd = (pt1.y() - pt2.y()) * (pt1.y() - pt2.y());
return std::sqrt(hd + vd);
}
QPointF getLineStart(const QPointF& pt1, const QPointF& pt2)
{
QPointF pt;
float rat = 10.0 / distance(pt1, pt2);
if (rat > 0.5) {
rat = 0.5;
}
pt.setX((1.0 - rat) * pt1.x() + rat * pt2.x());
pt.setY((1.0 - rat) * pt1.y() + rat * pt2.y());
return pt;
}
QPointF getLineEnd(const QPointF& pt1, const QPointF& pt2)
{
QPointF pt;
float rat = 10.0 / distance(pt1, pt2);
if (rat > 0.5) {
rat = 0.5;
}
pt.setX(rat * pt1.x() + (1.0 - rat)*pt2.x());
pt.setY(rat * pt1.y() + (1.0 - rat)*pt2.y());
return pt;
}
}
void PainterPath::smoothOut(const float& factor)
{
QList<QPointF> points;
QPointF p;
for (int i = 0; i < mPath->elementCount() - 1; i++) {
p = QPointF(mPath->elementAt(i).x, mPath->elementAt(i).y);
// Except for first and last points, check what the distance between two
// points is and if its less then min, don't add them to the list.
if (points.count() > 1 && (i < mPath->elementCount() - 2) && (distance(points.last(), p) < factor)) {
continue;
}
points.append(p);
}
// Don't proceed if we only have 3 or less points.
if (points.count() < 3) {
return;
}
QPointF pt1;
QPointF pt2;
QPainterPath* path = new QPainterPath();
for (int i = 0; i < points.count() - 1; i++) {
pt1 = getLineStart(points[i], points[i + 1]);
if (i == 0) {
path->moveTo(pt1);
} else {
path->quadTo(points[i], pt1);
}
pt2 = getLineEnd(points[i], points[i + 1]);
path->lineTo(pt2);
}
delete mPath;
mPath = path;
prepareGeometryChange();
}
Я не думаю, что в Qt 4.8 нет готового решения (как вы заметили, QSplineSeries
это особенность Qt 5.x) Также QSplineSeries
это часть QtCharts
модуль, который является коммерческим (например, QtDataVisualization
) поэтому, если у вас нет коммерческой лицензии или ваш проект под лицензией GPL, вы не сможете ее использовать.
Вы должны сделать это вручную, то есть пройти математику, необходимую для этого, и реализовать ее самостоятельно (или найти хорошую реализацию (необязательно быть даже в C ++, не говоря уже о Qt-совместимой)).
Поскольку вы упомянули кривые Безье, я бы предложил дать составная кривая Безье выстрел. Я помню, как реализовывал это для проекта, над которым работал. Это потребовало некоторой … работы. : D Эта статья может помочь вам начать.
Кривые Безье на самом деле являются B-сплайнами (если я правильно помню). Особенно, если вы можете справиться с определенным отсутствием гладкости, вы можете генерировать составные кривые Безье довольно быстро. Благодаря их надежности и популярности я на 100% уверен, что вы сможете найти достойную реализацию в Интернете. Вероятно, не Qt-friendly, но если написано правильно, вы сможете быстро адаптировать код.
это выглядит довольно многообещающе (это в ActionScript, но мне не нравится). Или вы можете дать QPainterPath::cubicTo()
снимок, который может создать кривые Безье для вас, учитывая, что вы также можете предоставить две контрольные точки, необходимые для расчета кривой.
Практически все используют кубическую интерполяцию для этой задачи, и ваш выбор — кривая Безье или сплайн Катмулла-Рома. Если вам нужно попасть в каждую точку, то вам нужно держать «ручки» или линию между контрольными точками Безье прямыми. Затем вы подходите по методу наименьших квадратов, что, как вы выяснили, немного связано.
Преимущество сплайнов Catmull Rom заключается в том, что им нужны только две дополнительные контрольные точки (начало и конец, просто зеркальные точки для их создания). Пока точки достаточно гладкие, линия будет вести себя хорошо. Маловероятно, что графика QT будет рисовать сплайны CatMull Rom напрямую, так что конвертируйте в Безье, это стандартный опубликованный метод, вы можете легко перейти от Catmull Rom к Безье, хотя и не наоборот — не каждый Безье может быть представлен в Catmull Rom с только несколько баллов.
Вы можете использовать другие методы интерполяции, например, quintic, если кубики не дадут вам желаемой кривой.