X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=src%2Fbeziercurve%2Fbeziersplineeditor.cpp;h=67097872c9440a3c9288cb72398fd844f11c7f6b;hb=bdd05090054b10dc93f0d9e2de9edbf262d06a07;hp=9846ee1fb36debe40f5b79b18e087b6e4f584e60;hpb=50f593828ee4b83d1694207476c77135045b314b;p=kdenlive diff --git a/src/beziercurve/beziersplineeditor.cpp b/src/beziercurve/beziersplineeditor.cpp index 9846ee1f..67097872 100644 --- a/src/beziercurve/beziersplineeditor.cpp +++ b/src/beziercurve/beziersplineeditor.cpp @@ -17,16 +17,20 @@ ***************************************************************************/ #include "beziersplineeditor.h" +#include "kdenlivesettings.h" #include #include -#include BezierSplineEditor::BezierSplineEditor(QWidget* parent) : QWidget(parent), - m_zoomLevel(0), m_mode(ModeNormal), + m_zoomLevel(0), + m_gridLines(3), + m_showAllHandles(true), + m_pixmapCache(NULL), + m_pixmapIsDirty(true), m_currentPointIndex(-1) { setMouseTracking(true); @@ -36,6 +40,12 @@ BezierSplineEditor::BezierSplineEditor(QWidget* parent) : setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); } +BezierSplineEditor::~BezierSplineEditor() +{ + if (m_pixmapCache) + delete m_pixmapCache; +} + CubicBezierSpline BezierSplineEditor::spline() { return m_spline; @@ -43,110 +53,204 @@ CubicBezierSpline BezierSplineEditor::spline() void BezierSplineEditor::setSpline(const CubicBezierSpline& spline) { - // TODO: cleanup - m_spline.fromString(spline.toString()); + m_spline = spline; + m_currentPointIndex = -1; + m_mode = ModeNormal; + emit currentPoint(BPoint()); + emit modified(); + update(); } BPoint BezierSplineEditor::getCurrentPoint() { if (m_currentPointIndex >= 0) - return m_spline.points()[m_currentPointIndex]; + return m_spline.getPoint(m_currentPointIndex); else return BPoint(); } -void BezierSplineEditor::updateCurrentPoint(const BPoint& p) +void BezierSplineEditor::updateCurrentPoint(const BPoint& p, bool final) { if (m_currentPointIndex >= 0) { m_spline.setPoint(m_currentPointIndex, p); // during validation the point might have changed - emit currentPoint(m_spline.points()[m_currentPointIndex]); + emit currentPoint(m_spline.getPoint(m_currentPointIndex)); + if (final) + emit modified(); update(); } } +void BezierSplineEditor::setPixmap(const QPixmap& pixmap) +{ + m_pixmap = pixmap; + m_pixmapIsDirty = true; + update(); +} + void BezierSplineEditor::slotZoomIn() { m_zoomLevel = qMax(m_zoomLevel-1, 0); + m_pixmapIsDirty = true; update(); } void BezierSplineEditor::slotZoomOut() { m_zoomLevel = qMin(m_zoomLevel+1, 3); + m_pixmapIsDirty = true; + update(); +} + +void BezierSplineEditor::setShowAllHandles(bool show) +{ + m_showAllHandles = show; + update(); +} + +int BezierSplineEditor::gridLines() +{ + return m_gridLines; +} + +void BezierSplineEditor::setGridLines(int lines) +{ + m_gridLines = qBound(0, lines, 8); update(); } void BezierSplineEditor::paintEvent(QPaintEvent* event) { - Q_UNUSED(event); + Q_UNUSED(event) QPainter p(this); - p.fillRect(rect(), palette().background()); - + /* + * Zoom + */ int wWidth = width() - 1; int wHeight = height() - 1; - int offset = 1/8. * m_zoomLevel * (wWidth > wHeight ? wWidth : wHeight); - wWidth -= 2 * offset; - wHeight -= 2 * offset; + int offsetX = 1/8. * m_zoomLevel * wWidth; + int offsetY = 1/8. * m_zoomLevel * wHeight; + wWidth -= 2 * offsetX; + wHeight -= 2 * offsetY; + + p.translate(offsetX, offsetY); + + /* + * Background + */ + p.fillRect(rect().translated(-offsetX, -offsetY), palette().background()); + if (!m_pixmap.isNull()) { + if (m_pixmapIsDirty || !m_pixmapCache) { + if (m_pixmapCache) + delete m_pixmapCache; + m_pixmapCache = new QPixmap(wWidth + 1, wHeight + 1); + QPainter cachePainter(m_pixmapCache); + + cachePainter.scale(1.0*(wWidth+1) / m_pixmap.width(), 1.0*(wHeight+1) / m_pixmap.height()); + cachePainter.drawPixmap(0, 0, m_pixmap); + m_pixmapIsDirty = false; + } + p.drawPixmap(0, 0, *m_pixmapCache); + } + + + p.setPen(QPen(Qt::gray, 1, Qt::SolidLine)); + + /* + * Borders + */ + if (m_zoomLevel != 0) { + p.drawRect(0, 0, wWidth, wHeight); + } + + /* + * Grid + */ + if (m_gridLines != 0) { + double stepH = wWidth / (double)(m_gridLines + 1); + double stepV = wHeight / (double)(m_gridLines + 1); + for (int i = 1; i <= m_gridLines; ++i) { + p.drawLine(QLineF(i * stepH, 0, i * stepH, wHeight)); + p.drawLine(QLineF(0, i * stepV, wWidth, i * stepV)); + } + } + + p.setRenderHint(QPainter::Antialiasing); /* * Standard line */ - /*p.setPen(QPen(Qt::gray, 1, Qt::SolidLine)); - p.drawLine(QLineF(0, wHeight, wWidth, 0));*/ + p.drawLine(QLineF(0, wHeight, wWidth, 0)); + + + /* + * Prepare Spline, Points + */ + int max = m_spline.points().count() - 1; + if (max < 1) + return; + BPoint point(m_spline.getPoint(0, wWidth, wHeight, true)); /* * Spline */ - double prevY = wHeight - m_spline.value(0.) * wHeight; - double prevX = 0.; - double curY; - double normalizedX = -1; - int x; + BPoint next; - p.setPen(QPen(Qt::black, 1, Qt::SolidLine)); - for (x = 0 ; x < wWidth ; ++x) { - normalizedX = x / (double)wWidth; - curY = wHeight - m_spline.value(normalizedX, true) * wHeight; - - /* - * Keep in mind that QLineF rounds doubles - * to ints mathematically, not just rounds down - * like in C - */ - p.drawLine(QLineF(prevX, prevY, - x, curY).translated(offset, offset)); - prevX = x; - prevY = curY; + QPainterPath splinePath(QPointF(point.p.x(), point.p.y())); + for (int i = 0; i < max; ++i) { + point = m_spline.getPoint(i, wWidth, wHeight, true); + next = m_spline.getPoint(i + 1, wWidth, wHeight, true); + splinePath.cubicTo(point.h2, next.h1, next.p); } - p.drawLine(QLineF(prevX, prevY , - x, wHeight - m_spline.value(1.0, true) * wHeight).translated(offset, offset)); + p.setPen(QPen(Qt::black, 1, Qt::SolidLine)); + p.drawPath(splinePath); + /* * Points + Handles */ p.setPen(QPen(Qt::red, 1, Qt::SolidLine)); - BPoint point; - QPolygon handle(4); - handle.setPoints(4, - 1, -2, - 4, 1, - 1, 4, - -2, 1); - for (int i = 0; i < m_spline.points().count(); ++i) { - point = m_spline.points().at(i); + + QPolygonF handle = QPolygonF() << QPointF(0, -3) << QPointF(3, 0) << QPointF(0, 3) << QPointF(-3, 0); +#if QT_VERSION < 0x040600 + QPolygonF tmp; +#endif + + for (int i = 0; i <= max; ++i) { + point = m_spline.getPoint(i, wWidth, wHeight, true); + if (i == m_currentPointIndex) { + // selected point: fill p and handles p.setBrush(QBrush(QColor(Qt::red), Qt::SolidPattern)); - p.drawLine(QLineF(point.h1.x() * wWidth, wHeight - point.h1.y() * wHeight, point.p.x() * wWidth, wHeight - point.p.y() * wHeight).translated(offset, offset)); - p.drawLine(QLineF(point.p.x() * wWidth, wHeight - point.p.y() * wHeight, point.h2.x() * wWidth, wHeight - point.h2.y() * wHeight).translated(offset, offset)); + // connect p and handles with lines + if (i != 0) + p.drawLine(QLineF(point.h1.x(), point.h1.y(), point.p.x(),point.p.y())); + if (i != max) + p.drawLine(QLineF(point.p.x(), point.p.y(), point.h2.x(), point.h2.y())); } - p.drawEllipse(QRectF(point.p.x() * wWidth - 3, - wHeight - 3 - point.p.y() * wHeight, 6, 6).translated(offset, offset)); - p.drawConvexPolygon(handle.translated(point.h1.x() * wWidth, wHeight - point.h1.y() * wHeight).translated(offset, offset)); - p.drawConvexPolygon(handle.translated(point.h2.x() * wWidth, wHeight - point.h2.y() * wHeight).translated(offset, offset)); + p.drawEllipse(QRectF(point.p.x() - 3, + point.p.y() - 3, 6, 6)); + if (i != 0 && (i == m_currentPointIndex || m_showAllHandles)) { +#if QT_VERSION >= 0x040600 + p.drawConvexPolygon(handle.translated(point.h1.x(), point.h1.y())); +#else + tmp = handle; + tmp.translate(point.h1.x(), point.h1.y()); + p.drawConvexPolygon(tmp); +#endif + } + if (i != max && (i == m_currentPointIndex || m_showAllHandles)) { +#if QT_VERSION >= 0x040600 + p.drawConvexPolygon(handle.translated(point.h2.x(), point.h2.y())); +#else + tmp = handle; + tmp.translate(point.h2.x(), point.h2.y()); + p.drawConvexPolygon(tmp); +#endif + } if ( i == m_currentPointIndex) p.setBrush(QBrush(Qt::NoBrush)); @@ -155,7 +259,7 @@ void BezierSplineEditor::paintEvent(QPaintEvent* event) void BezierSplineEditor::resizeEvent(QResizeEvent* event) { - m_spline.setPrecision(width()); + m_pixmapIsDirty = true; QWidget::resizeEvent(event); } @@ -163,12 +267,13 @@ void BezierSplineEditor::mousePressEvent(QMouseEvent* event) { int wWidth = width() - 1; int wHeight = height() - 1; - int offset = 1/8. * m_zoomLevel * (wWidth > wHeight ? wWidth : wHeight); - wWidth -= 2 * offset; - wHeight -= 2 * offset; + int offsetX = 1/8. * m_zoomLevel * wWidth; + int offsetY = 1/8. * m_zoomLevel * wHeight; + wWidth -= 2 * offsetX; + wHeight -= 2 * offsetY; - double x = (event->pos().x() - offset) / (double)(wWidth); - double y = 1.0 - (event->pos().y() - offset) / (double)(wHeight); + double x = (event->pos().x() - offsetX) / (double)(wWidth); + double y = 1.0 - (event->pos().y() - offsetY) / (double)(wHeight); point_types selectedPoint; int closestPointIndex = nearestPointInRange(QPointF(x, y), wWidth, wHeight, &selectedPoint); @@ -181,7 +286,7 @@ void BezierSplineEditor::mousePressEvent(QMouseEvent* event) --m_currentPointIndex; update(); if (m_currentPointIndex >= 0) - emit currentPoint(m_spline.points()[m_currentPointIndex]); + emit currentPoint(m_spline.getPoint(m_currentPointIndex)); else emit currentPoint(BPoint()); emit modified(); @@ -191,49 +296,28 @@ void BezierSplineEditor::mousePressEvent(QMouseEvent* event) } if (closestPointIndex < 0) { - BPoint po; - po.p = QPointF(x, y); - po.h1 = QPointF(x-0.05, y-0.05); - po.h2 = QPointF(x+0.05, y+0.05); - m_currentPointIndex = m_spline.addPoint(po); + m_currentPointIndex = m_spline.addPoint(BPoint(QPointF(x-0.05, y-0.05), + QPointF(x, y), + QPointF(x+0.05, y+0.05))); m_currentPointType = PTypeP; - /*if (!d->jumpOverExistingPoints(newPoint, -1)) return;*/ } else { m_currentPointIndex = closestPointIndex; m_currentPointType = selectedPoint; } - BPoint point = m_spline.points()[m_currentPointIndex]; - QPointF p; - switch (m_currentPointType) { - case PTypeH1: - p = point.h1; - break; - case PTypeP: - p = point.p; - break; - case PTypeH2: - p = point.h2; - } + BPoint point = m_spline.getPoint(m_currentPointIndex); - m_grabOriginalX = p.x(); - m_grabOriginalY = p.y(); - m_grabOffsetX = p.x() - x; - m_grabOffsetY = p.y() - y; + m_grabPOriginal = point; + if (m_currentPointIndex > 0) + m_grabPPrevious = m_spline.getPoint(m_currentPointIndex - 1); + if (m_currentPointIndex < m_spline.points().count() - 1) + m_grabPNext = m_spline.getPoint(m_currentPointIndex + 1); + m_grabOffsetX = point[(int)m_currentPointType].x() - x; + m_grabOffsetY = point[(int)m_currentPointType].y() - y; - switch (m_currentPointType) { - case PTypeH1: - point.h1 = QPointF(x + m_grabOffsetX, y + m_grabOffsetY); - break; - case PTypeP: - point.p = QPointF(x + m_grabOffsetX, y + m_grabOffsetY); - break; - case PTypeH2: - point.h2 = QPointF(x + m_grabOffsetX, y + m_grabOffsetY); - } - m_spline.setPoint(m_currentPointIndex, point); + point[(int)m_currentPointType] = QPointF(x + m_grabOffsetX, y + m_grabOffsetY); - //d->m_draggedAwayPointIndex = -1; + m_spline.setPoint(m_currentPointIndex, point); m_mode = ModeDrag; @@ -256,108 +340,120 @@ void BezierSplineEditor::mouseMoveEvent(QMouseEvent* event) { int wWidth = width() - 1; int wHeight = height() - 1; - int offset = 1/8. * m_zoomLevel * (wWidth > wHeight ? wWidth : wHeight); - wWidth -= 2 * offset; - wHeight -= 2 * offset; + int offsetX = 1/8. * m_zoomLevel * wWidth; + int offsetY = 1/8. * m_zoomLevel * wHeight; + wWidth -= 2 * offsetX; + wHeight -= 2 * offsetY; - double x = (event->pos().x() - offset) / (double)(wWidth); - double y = 1.0 - (event->pos().y() - offset) / (double)(wHeight); + double x = (event->pos().x() - offsetX) / (double)(wWidth); + double y = 1.0 - (event->pos().y() - offsetY) / (double)(wHeight); - if (m_mode == ModeNormal) { // If no point is selected set the the cursor shape if on top + if (m_mode == ModeNormal) { + // If no point is selected set the the cursor shape if on top point_types type; int nearestPointIndex = nearestPointInRange(QPointF(x, y), wWidth, wHeight, &type); - + if (nearestPointIndex < 0) setCursor(Qt::ArrowCursor); else setCursor(Qt::CrossCursor); - } else { // Else, drag the selected point - /*bool crossedHoriz = event->pos().x() - width() > MOUSE_AWAY_THRES || - event->pos().x() < -MOUSE_AWAY_THRES; - bool crossedVert = event->pos().y() - height() > MOUSE_AWAY_THRES || - event->pos().y() < -MOUSE_AWAY_THRES; - - bool removePoint = (crossedHoriz || crossedVert); - - if (!removePoint && d->m_draggedAwayPointIndex >= 0) { - // point is no longer dragged away so reinsert it - QPointF newPoint(d->m_draggedAwayPoint); - d->m_grab_point_index = d->m_curve.addPoint(newPoint); - d->m_draggedAwayPointIndex = -1; - } - - if (removePoint && - (d->m_draggedAwayPointIndex >= 0)) - return; - */ - + } else { + // Else, drag the selected point setCursor(Qt::CrossCursor); - + x += m_grabOffsetX; y += m_grabOffsetY; - - double leftX, rightX; - BPoint point = m_spline.points()[m_currentPointIndex]; + + double leftX = 0.; + double rightX = 1.; + BPoint point = m_spline.getPoint(m_currentPointIndex); switch (m_currentPointType) { case PTypeH1: rightX = point.p.x(); if (m_currentPointIndex == 0) - leftX = -1000; + leftX = -4; else - leftX = m_spline.points()[m_currentPointIndex - 1].p.x(); + leftX = m_spline.getPoint(m_currentPointIndex - 1).p.x(); + x = qBound(leftX, x, rightX); - point.h1 = QPointF(x, y); + point.setH1(QPointF(x, y)); break; + case PTypeP: - if (m_currentPointIndex == 0) { - leftX = 0.0; + if (m_currentPointIndex == 0) rightX = 0.0; - /*if (d->m_curve.points().count() > 1) - * rightX = d->m_curve.points()[d->m_grab_point_index + 1].x() - POINT_AREA; - * else - * rightX = 1.0;*/ - } else if (m_currentPointIndex == m_spline.points().count() - 1) { - leftX = 1.0;//m_spline.points()[m_currentPointIndex - 1].p.x(); - rightX = 1.0; - } else { - //// the 1E-4 addition so we can grab the dot later. - leftX = m_spline.points()[m_currentPointIndex - 1].p.x();// + POINT_AREA; - rightX = m_spline.points()[m_currentPointIndex + 1].p.x();// - POINT_AREA; - } + else if (m_currentPointIndex == m_spline.points().count() - 1) + leftX = 1.0; + x = qBound(leftX, x, rightX); y = qBound(0., y, 1.); - // move handles by same offset - point.h1 += QPointF(x, y) - point.p; - point.h2 += QPointF(x, y) - point.p; + // handles might have changed because we neared another point + // try to restore + point.h1 = m_grabPOriginal.h1; + point.h2 = m_grabPOriginal.h2; + // and move by same offset + // (using update handle in point.setP won't work because the offset between new and old point is very small) + point.h1 += QPointF(x, y) - m_grabPOriginal.p; + point.h2 += QPointF(x, y) - m_grabPOriginal.p; - point.p = QPointF(x, y); + point.setP(QPointF(x, y), false); break; + case PTypeH2: leftX = point.p.x(); if (m_currentPointIndex == m_spline.points().count() - 1) - rightX = 1001; + rightX = 5; else - rightX = m_spline.points()[m_currentPointIndex + 1].p.x(); + rightX = m_spline.getPoint(m_currentPointIndex + 1).p.x(); + x = qBound(leftX, x, rightX); - point.h2 = QPointF(x, y); + point.setH2(QPointF(x, y)); }; - m_spline.setPoint(m_currentPointIndex, point); - - /*if (removePoint && d->m_curve.points().count() > 2) { - d->m_draggedAwayPoint = d->m_curve.points()[d->m_grab_point_index]; - d->m_draggedAwayPointIndex = d->m_grab_point_index; - d->m_curve.removePoint(d->m_grab_point_index); - d->m_grab_point_index = bounds(d->m_grab_point_index, 0, d->m_curve.points().count() - 1); + int index = m_currentPointIndex; + m_currentPointIndex = m_spline.setPoint(m_currentPointIndex, point); + + if (m_currentPointType == PTypeP) { + // we might have changed the handles of other points + // try to restore + if (index == m_currentPointIndex) { + if (m_currentPointIndex > 0) + m_spline.setPoint(m_currentPointIndex - 1, m_grabPPrevious); + if (m_currentPointIndex < m_spline.points().count() -1) + m_spline.setPoint(m_currentPointIndex + 1, m_grabPNext); + } else { + if (m_currentPointIndex < index) { + m_spline.setPoint(index, m_grabPPrevious); + m_grabPNext = m_grabPPrevious; + if (m_currentPointIndex > 0) + m_grabPPrevious = m_spline.getPoint(m_currentPointIndex - 1); + } else { + m_spline.setPoint(index, m_grabPNext); + m_grabPPrevious = m_grabPNext; + if (m_currentPointIndex < m_spline.points().count() - 1) + m_grabPNext = m_spline.getPoint(m_currentPointIndex + 1); + } + } } - - d->setCurveModified();*/ + emit currentPoint(point); + if (KdenliveSettings::dragvalue_directupdate()) + emit modified(); update(); } } +void BezierSplineEditor::mouseDoubleClickEvent(QMouseEvent* /*event*/) +{ + if (m_currentPointIndex >= 0) { + BPoint p = m_spline.getPoint(m_currentPointIndex); + p.handlesLinked = !p.handlesLinked; + m_spline.setPoint(m_currentPointIndex, p); + emit currentPoint(p); + } +} + void BezierSplineEditor::leaveEvent(QEvent* event) { QWidget::leaveEvent(event); @@ -366,47 +462,28 @@ void BezierSplineEditor::leaveEvent(QEvent* event) int BezierSplineEditor::nearestPointInRange(QPointF p, int wWidth, int wHeight, BezierSplineEditor::point_types* sel) { double nearestDistanceSquared = 1000; - point_types selectedPoint; + point_types selectedPoint = PTypeP; int nearestIndex = -1; int i = 0; double distanceSquared; + // find out distance using the Pythagorean theorem foreach(const BPoint & point, m_spline.points()) { - distanceSquared = pow(point.h1.x() - p.x(), 2) + pow(point.h1.y() - p.y(), 2); - if (distanceSquared < nearestDistanceSquared) { - nearestIndex = i; - nearestDistanceSquared = distanceSquared; - selectedPoint = PTypeH1; - } - distanceSquared = pow(point.p.x() - p.x(), 2) + pow(point.p.y() - p.y(), 2); - if (distanceSquared < nearestDistanceSquared) { - nearestIndex = i; - nearestDistanceSquared = distanceSquared; - selectedPoint = PTypeP; - } - distanceSquared = pow(point.h2.x() - p.x(), 2) + pow(point.h2.y() - p.y(), 2); - if (distanceSquared < nearestDistanceSquared) { - nearestIndex = i; - nearestDistanceSquared = distanceSquared; - selectedPoint = PTypeH2; + for (int j = 0; j < 3; ++j) { + distanceSquared = pow(point[j].x() - p.x(), 2) + pow(point[j].y() - p.y(), 2); + if (distanceSquared < nearestDistanceSquared) { + nearestIndex = i; + nearestDistanceSquared = distanceSquared; + selectedPoint = (point_types)j; + } } ++i; } - if (nearestIndex >= 0) { - BPoint point = m_spline.points()[nearestIndex]; - QPointF p2; - switch (selectedPoint) { - case PTypeH1: - p2 = point.h1; - break; - case PTypeP: - p2 = point.p; - break; - case PTypeH2: - p2 = point.h2; - } - if (qAbs(p.x() - p2.x()) * wWidth < 5 && qAbs(p.y() - p2.y()) * wHeight < 5) { + if (nearestIndex >= 0 && (nearestIndex == m_currentPointIndex || selectedPoint == PTypeP || m_showAllHandles)) { + // a point was found and it is not a hidden handle + BPoint point = m_spline.getPoint(nearestIndex); + if (qAbs(p.x() - point[(int)selectedPoint].x()) * wWidth < 5 && qAbs(p.y() - point[(int)selectedPoint].y()) * wHeight < 5) { *sel = selectedPoint; return nearestIndex; }