]> git.sesse.net Git - kdenlive/commitdiff
Add bézier color curves GUI (WIP)
authorTill Theato <root@ttill.de>
Fri, 31 Dec 2010 14:06:34 +0000 (14:06 +0000)
committerTill Theato <root@ttill.de>
Fri, 31 Dec 2010 14:06:34 +0000 (14:06 +0000)
svn path=/trunk/kdenlive/; revision=5225

data/kdenliveeffectscategory.rc
effects/CMakeLists.txt
effects/frei0r_bezier_curves.xml [new file with mode: 0644]
src/CMakeLists.txt
src/beziercurve/CMakeLists.txt [new file with mode: 0644]
src/beziercurve/beziersplinewidget.cpp [new file with mode: 0644]
src/beziercurve/beziersplinewidget.h [new file with mode: 0644]
src/beziercurve/cubicbezierspline.cpp [new file with mode: 0644]
src/beziercurve/cubicbezierspline.h [new file with mode: 0644]
src/effectstackedit.cpp

index 6cad2c7b70597e27bcc38bd551712413646a6d47..eee3fddc653b2bd95f0926b2db38f295cc74fe69 100644 (file)
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <!DOCTYPE kpartgui SYSTEM "kpartgui.dtd">
 <data name="effects" version="0">
-  <group list="brightness,gamma,frei0r.balanc0r,frei0r.brightness,frei0r.levels,frei0r.three_point_balance,frei0r.curves,frei0r.coloradj_RGB,frei0r.sopsat">
+  <group list="brightness,gamma,frei0r.balanc0r,frei0r.brightness,frei0r.levels,frei0r.three_point_balance,frei0r.curves,frei0r.coloradj_RGB,frei0r.sopsat,frei0r.bezier_curves">
     <text>Colour correction</text>
   </group>
   <group list="invert,sepia,tcolor,greyscale,frei0r.B,frei0r.G,frei0r.R,frei0r.contrast0r,frei0r.saturat0r,frei0r.tint0r,frei0r.primaries,frei0r.rgbparade,chroma_hold,frei0r.hueshift0r">
index 8a3c6b88ea66db2cf26c53324d8d053a38cf2dcd..643c51ccefcf73acc297a774e44060f464f882e5 100644 (file)
@@ -51,6 +51,7 @@ frei0r_alpha0ps.xml
 frei0r_alphagrad.xml
 frei0r_alphaspot.xml
 frei0r_balanc0r.xml
+frei0r_bezier_curves.xml
 frei0r_brightness.xml
 frei0r_coloradj_rgb.xml
 frei0r_colordistance.xml
diff --git a/effects/frei0r_bezier_curves.xml b/effects/frei0r_bezier_curves.xml
new file mode 100644 (file)
index 0000000..07d9a69
--- /dev/null
@@ -0,0 +1,18 @@
+<!DOCTYPE kpartgui>
+<effect tag="frei0r.curves" id="frei0r.bezier_curves">
+        <name>Bézier Curves</name>
+        <description>Color curves adjustment</description>
+        <author>Maksim Golovkin, Till Theato</author>
+
+        <parameter type="list" name="Channel" default="0" paramlist="0,1,2,3" paramlistdisplay="Red,Green,Blue,Luma">
+                <name>Channel</name>
+        </parameter>
+
+        <parameter type="list" name="Luma formula" default="1" paramlist="0,1" paramlistdisplay="Rec. 601,Rec. 709">
+                <name>Luma formula</name>
+        </parameter>
+
+        <parameter type="bezier_spline" name="Bézier spline" depends="Channel" default="0;0#0;0#0;0.5|1;0.5#1;1#1;1">
+                <name>Bézier Curve(Spline) Widget</name>
+        </parameter>
+</effect>
index a48f6cbfae2171d564372368ac31c55ac0ac2b83..9952bd0ceac672ad6b5eb64ae37414e4d49bcae9 100644 (file)
@@ -1,6 +1,9 @@
-add_subdirectory(widgets)
-add_subdirectory(mimetypes)
+add_subdirectory(beziercurve)
+add_subdirectory(colorcorrection)
 add_subdirectory(kiss_fft)
+add_subdirectory(mimetypes)
+add_subdirectory(onmonitoritems)
+add_subdirectory(widgets)
 
 macro_optional_find_package(Nepomuk)
 include(FindQImageBlitz)
@@ -14,11 +17,12 @@ include_directories(
   ${QDBUS_INCLUDE_DIRS}
   ${CMAKE_SOURCE_DIR}
   ${CMAKE_BINARY_DIR}
-  ${CMAKE_SOURCE_DIR}/src/widgets
+  ${CMAKE_SOURCE_DIR}/src/audioscopes
+  ${CMAKE_SOURCE_DIR}/src/beziercurve
   ${CMAKE_SOURCE_DIR}/src/colorcorrection
-  ${CMAKE_SOURCE_DIR}/src/onmonitoritems
   ${CMAKE_SOURCE_DIR}/src/kiss_fft
-  ${CMAKE_SOURCE_DIR}/src/audioscopes
+  ${CMAKE_SOURCE_DIR}/src/onmonitoritems
+  ${CMAKE_SOURCE_DIR}/src/widgets
 )
 
 
@@ -258,11 +262,10 @@ set(kdenlive_SRCS
   kiss_fft/_kiss_fft_guts.h
   kiss_fft/kiss_fft.c
   kiss_fft/tools/kiss_fftr.c
+  beziercurve/cubicbezierspline.cpp
+  beziercurve/beziersplinewidget.cpp
 )
 
-add_subdirectory(${CMAKE_SOURCE_DIR}/src/colorcorrection)
-add_subdirectory(${CMAKE_SOURCE_DIR}/src/onmonitoritems)
-
 add_definitions(${KDE4_DEFINITIONS})
 
 if(APPLE OR CMAKE_SYSTEM_NAME STREQUAL "FreeBSD" OR CMAKE_SYSTEM_NAME STREQUAL "NetBSD" OR NO_JOGSHUTTLE)
diff --git a/src/beziercurve/CMakeLists.txt b/src/beziercurve/CMakeLists.txt
new file mode 100644 (file)
index 0000000..8b13789
--- /dev/null
@@ -0,0 +1 @@
+
diff --git a/src/beziercurve/beziersplinewidget.cpp b/src/beziercurve/beziersplinewidget.cpp
new file mode 100644 (file)
index 0000000..3ec2bc2
--- /dev/null
@@ -0,0 +1,338 @@
+/***************************************************************************
+ *   Copyright (C) 2010 by Till Theato (root@ttill.de)                     *
+ *   This file is part of Kdenlive (www.kdenlive.org).                     *
+ *                                                                         *
+ *   Kdenlive is free software: you can redistribute it and/or modify      *
+ *   it under the terms of the GNU General Public License as published by  *
+ *   the Free Software Foundation, either version 2 of the License, or     *
+ *   (at your option) any later version.                                   *
+ *                                                                         *
+ *   Kdenlive is distributed in the hope that it will be useful,           *
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
+ *   GNU General Public License for more details.                          *
+ *                                                                         *
+ *   You should have received a copy of the GNU General Public License     *
+ *   along with Kdenlive.  If not, see <http://www.gnu.org/licenses/>.     *
+ ***************************************************************************/
+
+#include "beziersplinewidget.h"
+
+#include <QPainter>
+#include <QMouseEvent>
+
+#include <KDebug>
+
+BezierSplineWidget::BezierSplineWidget(QWidget* parent) :
+        QWidget(parent),
+        m_mode(ModeNormal),
+        m_currentPointIndex(-1)
+{
+    //m_spline.setPrecision(width());
+    setMouseTracking(true);
+    setAutoFillBackground(false);
+    setAttribute(Qt::WA_OpaquePaintEvent);
+    setMinimumSize(150, 150);
+    setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
+}
+
+CubicBezierSpline BezierSplineWidget::spline()
+{
+    return m_spline;
+}
+
+void BezierSplineWidget::setSpline(const CubicBezierSpline& spline)
+{
+    // TODO: cleanup
+    m_spline.fromString(spline.toString());
+}
+
+void BezierSplineWidget::paintEvent(QPaintEvent* event)
+{
+    Q_UNUSED(event);
+
+    QPainter p(this);
+
+    p.fillRect(rect(), palette().background());
+
+    int    wWidth = width() - 1;
+    int    wHeight = height() - 1;
+
+    // Draw curve.
+    double prevY = wHeight - m_spline.value(0.) * wHeight;
+    double prevX = 0.;
+    double curY;
+    double normalizedX = -1;
+    int x;
+    
+    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));
+        prevX = x;
+        prevY = curY;
+    }
+    p.drawLine(QLineF(prevX, prevY ,
+                      x, wHeight - m_spline.value(1.0, true) * wHeight));
+    
+    // Drawing curve handles.
+    p.setPen(QPen(Qt::red, 1, Qt::SolidLine));
+    BPoint point;
+    for (int i = 0; i < m_spline.points().count(); ++i) {
+        point = m_spline.points().at(i);
+        p.drawEllipse(QRectF(point.h1.x() * wWidth - 3,
+                            wHeight - 3 - point.h1.y() * wHeight, 6, 6));
+        p.drawEllipse(QRectF(point.p.x() * wWidth - 3,
+                             wHeight - 3 - point.p.y() * wHeight, 6, 6));
+        p.drawEllipse(QRectF(point.h2.x() * wWidth - 3,
+                             wHeight - 3 - point.h2.y() * wHeight, 6, 6));
+    }
+}
+
+void BezierSplineWidget::resizeEvent(QResizeEvent* event)
+{
+    m_spline.setPrecision(width());
+    QWidget::resizeEvent(event);
+}
+
+void BezierSplineWidget::mousePressEvent(QMouseEvent* event)
+{
+    double x = event->pos().x() / (double)(width() - 1);
+    double y = 1.0 - event->pos().y() / (double)(height() - 1);
+
+    point_types selectedPoint;
+    int closest_point_index = nearestPointInRange(QPointF(x, y), width(), height(), &selectedPoint);
+
+    /*if (event->button() == Qt::RightButton && closest_point_index > 0 && closest_point_index < d->m_curve.points().count() - 1) {
+        d->m_curve.removePoint(closest_point_index);
+        setCursor(Qt::ArrowCursor);
+        d->setState(ST_NORMAL);
+        if (closest_point_index < d->m_grab_point_index)
+            --d->m_grab_point_index;
+        d->setCurveModified();
+        return;
+    } else*/ if (event->button() != Qt::LeftButton) return;
+
+    if (closest_point_index < 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_currentPointType = PTypeP;
+        if (m_currentPointIndex == -1) // x already in use
+            return;
+        /*if (!d->jumpOverExistingPoints(newPoint, -1)) return;*/
+    } else {
+        m_currentPointIndex = closest_point_index;
+        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;
+    }
+
+    m_grabOriginalX = p.x();
+    m_grabOriginalY = p.y();
+    m_grabOffsetX = p.x() - x;
+    m_grabOffsetY = p.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);
+    
+    //d->m_draggedAwayPointIndex = -1;
+
+    m_mode = ModeDrag;
+
+    update();
+}
+
+void BezierSplineWidget::mouseReleaseEvent(QMouseEvent* event)
+{
+    if (event->button() != Qt::LeftButton)
+        return;
+
+    setCursor(Qt::ArrowCursor);
+    m_mode = ModeNormal;
+    
+    emit modified();
+}
+
+void BezierSplineWidget::mouseMoveEvent(QMouseEvent* event)
+{
+    double x = event->pos().x() / (double)(width() - 1);
+    double y = 1.0 - event->pos().y() / (double)(height() - 1);
+    
+    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), width(), height(), &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;
+        */
+        
+        setCursor(Qt::CrossCursor);
+        
+        x += m_grabOffsetX;
+        y += m_grabOffsetY;
+        
+        double leftX, rightX;
+        BPoint point = m_spline.points()[m_currentPointIndex];
+        switch (m_currentPointType) {
+        case PTypeH1:
+            rightX = point.p.x();
+            if (m_currentPointIndex == 0)
+                leftX = -1000;
+            else
+                leftX = m_spline.points()[m_currentPointIndex - 1].p.x();
+            x = qBound(leftX, x, rightX);
+            point.h1 = QPointF(x, y);
+            break;
+        case PTypeP:
+            if (m_currentPointIndex == 0) {
+                leftX = 0.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;
+            }
+            x = qBound(leftX, x, rightX);
+            y = qBound(0., y, 1.);
+            point.p = QPointF(x, y);
+            break;
+        case PTypeH2:
+            leftX = point.p.x();
+            if (m_currentPointIndex == m_spline.points().count() - 1)
+                rightX = 1001;
+            else
+                rightX = m_spline.points()[m_currentPointIndex + 1].p.x();
+            x = qBound(leftX, x, rightX);
+            point.h2 = 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);
+        }
+        
+        d->setCurveModified();*/
+        update();
+    }
+}
+
+void BezierSplineWidget::leaveEvent(QEvent* event)
+{
+    QWidget::leaveEvent(event);
+}
+
+int BezierSplineWidget::nearestPointInRange(QPointF p, int wWidth, int wHeight, BezierSplineWidget::point_types* sel)
+{
+    double nearestDistanceSquared = 1000;
+    point_types selectedPoint;
+    int nearestIndex = -1;
+    int i = 0;
+
+    double distanceSquared;
+    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;
+        }
+        ++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 - 1) < 5 && qAbs(p.y() - p2.y()) * (wHeight - 1) < 5) {
+            *sel = selectedPoint;
+            return nearestIndex;
+        }
+    }
+
+    return -1;
+}
+
+#include "beziersplinewidget.moc"
diff --git a/src/beziercurve/beziersplinewidget.h b/src/beziercurve/beziersplinewidget.h
new file mode 100644 (file)
index 0000000..245db96
--- /dev/null
@@ -0,0 +1,67 @@
+/***************************************************************************
+ *   Copyright (C) 2010 by Till Theato (root@ttill.de)                     *
+ *   This file is part of Kdenlive (www.kdenlive.org).                     *
+ *                                                                         *
+ *   Kdenlive is free software: you can redistribute it and/or modify      *
+ *   it under the terms of the GNU General Public License as published by  *
+ *   the Free Software Foundation, either version 2 of the License, or     *
+ *   (at your option) any later version.                                   *
+ *                                                                         *
+ *   Kdenlive is distributed in the hope that it will be useful,           *
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
+ *   GNU General Public License for more details.                          *
+ *                                                                         *
+ *   You should have received a copy of the GNU General Public License     *
+ *   along with Kdenlive.  If not, see <http://www.gnu.org/licenses/>.     *
+ ***************************************************************************/
+
+#ifndef BEZIERSPLINEWIDGET_H
+#define BEZIERSPLINEWIDGET_H
+
+#include "cubicbezierspline.h"
+
+#include <QtCore>
+#include <QWidget>
+
+class BezierSplineWidget : public QWidget
+{
+    Q_OBJECT
+
+public:
+    BezierSplineWidget(QWidget* parent = 0);
+
+    CubicBezierSpline spline();
+    void setSpline(const CubicBezierSpline &spline);
+
+protected:
+    //void keyPressEvent(QKeyEvent *event);
+    void paintEvent(QPaintEvent *event);
+    void mousePressEvent(QMouseEvent *event);
+    void mouseReleaseEvent(QMouseEvent * event);
+    void mouseMoveEvent(QMouseEvent * event);
+    void leaveEvent(QEvent *event);
+    void resizeEvent(QResizeEvent *event);
+
+private:
+    CubicBezierSpline m_spline;
+    enum modes { ModeDrag, ModeNormal };
+    enum point_types { PTypeH1, PTypeP, PTypeH2 };
+    modes m_mode;
+    int m_currentPointIndex;
+    point_types m_currentPointType;
+    double m_grabOffsetX;
+    double m_grabOffsetY;
+    double m_grabOriginalX;
+    double m_grabOriginalY;
+    //QPointF m_draggedAwayPoint;
+    //int m_draggedAwayPointIndex;
+
+    //inline void drawGrid(QPainter &p, int width, int height);
+    int nearestPointInRange(QPointF p, int wWidth, int wHeight, point_types *sel);
+
+signals:
+    void modified();
+};
+
+#endif
diff --git a/src/beziercurve/cubicbezierspline.cpp b/src/beziercurve/cubicbezierspline.cpp
new file mode 100644 (file)
index 0000000..c6d031f
--- /dev/null
@@ -0,0 +1,239 @@
+/***************************************************************************
+ *   Copyright (C) 2010 by Till Theato (root@ttill.de)                     *
+ *   This file is part of Kdenlive (www.kdenlive.org).                     *
+ *                                                                         *
+ *   Kdenlive is free software: you can redistribute it and/or modify      *
+ *   it under the terms of the GNU General Public License as published by  *
+ *   the Free Software Foundation, either version 2 of the License, or     *
+ *   (at your option) any later version.                                   *
+ *                                                                         *
+ *   Kdenlive is distributed in the hope that it will be useful,           *
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
+ *   GNU General Public License for more details.                          *
+ *                                                                         *
+ *   You should have received a copy of the GNU General Public License     *
+ *   along with Kdenlive.  If not, see <http://www.gnu.org/licenses/>.     *
+ ***************************************************************************/
+
+#include "cubicbezierspline.h"
+
+#include <KDebug>
+
+static bool pointLessThan(const BPoint &a, const BPoint &b)
+{
+    return a.p.x() < b.p.x();
+}
+
+CubicBezierSpline::CubicBezierSpline(QObject* parent) :
+        QObject(parent),
+        m_validSpline(false),
+        m_precision(100)
+{
+    BPoint start;
+    start.p.setX(0);
+    start.p.setY(0);
+    start.h1.setX(0);
+    start.h1.setY(0);
+    start.h2.setX(0.1);
+    start.h2.setY(0.1);
+    m_points.append(start);
+
+    BPoint end;
+    end.p.setX(1);
+    end.p.setY(1);
+    end.h1.setX(0.9);
+    end.h1.setY(0.9);
+    end.h2.setX(1);
+    end.h2.setY(1);
+    m_points.append(end);
+}
+
+CubicBezierSpline::CubicBezierSpline(const CubicBezierSpline& spline, QObject* parent) :
+        QObject(parent)
+{
+    m_precision = spline.m_precision;
+    m_points = spline.m_points;
+    m_validSpline = false;
+}
+
+CubicBezierSpline& CubicBezierSpline::operator=(const CubicBezierSpline& spline)
+{
+    m_precision = spline.m_precision;
+    m_points = spline.m_points;
+    return *this;
+}
+
+void CubicBezierSpline::fromString(const QString& spline)
+{
+    m_points.clear();
+    m_validSpline = false;
+
+    QStringList bpoints = spline.split('|');
+    foreach(const QString &bpoint, bpoints) {
+        QStringList points = bpoint.split('#');
+        QList <QPointF> values;
+        foreach(const QString &point, points) {
+            QStringList xy = point.split(';');
+            if (xy.count() == 2)
+                values.append(QPointF(xy.at(0).toDouble(), xy.at(1).toDouble()));
+        }
+        if (values.count() == 3) {
+            BPoint bp;
+            bp.h1 = values.at(0);
+            bp.p  = values.at(1);
+            bp.h2 = values.at(2);
+            m_points.append(bp);
+        }
+    }
+
+    keepSorted();
+    validatePoints();
+}
+
+QString CubicBezierSpline::toString() const
+{
+    QStringList spline;
+    foreach(const BPoint &p, m_points) {
+        spline << (QString::number(p.h1.x()) + ";" + QString::number(p.h1.y())
+                        + "#" + QString::number(p.p.x())  + ";" + QString::number(p.p.y())
+                        + "#" + QString::number(p.h2.x()) + ";" + QString::number(p.h2.y()));
+    }
+    return spline.join("|");
+}
+
+int CubicBezierSpline::setPoint(int ix, const BPoint& point)
+{
+    m_points[ix] = point;
+    keepSorted();
+    validatePoints();
+    m_validSpline = false;
+    return m_points.indexOf(point); // in case it changed
+}
+
+QList <BPoint> CubicBezierSpline::points()
+{
+    return m_points;
+}
+
+void CubicBezierSpline::removePoint(int ix)
+{
+    m_points.removeAt(ix);
+    m_validSpline = false;
+}
+
+int CubicBezierSpline::addPoint(const BPoint& point)
+{
+    m_points.append(point);
+    keepSorted();
+    validatePoints();
+    m_validSpline = false;
+    return m_points.indexOf(point);
+}
+
+void CubicBezierSpline::setPrecision(int pre)
+{
+    if (pre != m_precision) {
+        m_precision = pre;
+        m_validSpline = false;
+    }
+}
+
+int CubicBezierSpline::getPrecision()
+{
+    return m_precision;
+}
+
+qreal CubicBezierSpline::value(qreal x, bool cont)
+{
+    update();
+
+    //x = qBound(m_spline.constBegin().key(), x, m_spline.constEnd().key());
+    //kDebug() << "....x" << x<<"bounddown"<<m_spline.constBegin().key()<<"up"<<m_spline.constEnd().key();
+
+    if (!cont)
+        m_i = m_spline.constBegin();
+    if (m_i != m_spline.constBegin())
+        --m_i;
+
+    double diff = qAbs(x - m_i.key());
+    double y = m_i.value();
+    while (m_i != m_spline.constEnd()) {
+        if (qAbs(x - m_i.key()) > diff)
+            break;
+
+        diff = qAbs(x - m_i.key());
+        y = m_i.value();
+        ++m_i;
+    }
+    return qBound((qreal)0.0, y, (qreal)1.0);
+}
+
+void CubicBezierSpline::validatePoints()
+{
+    BPoint p1, p2;
+    for (int i = 0; i < m_points.count() - 1; ++i) {
+        p1 = m_points.at(i);
+        p2 = m_points.at(i+1);
+        p1.h2.setX(qBound(p1.p.x(), p1.h2.x(), p2.p.x()));
+        p2.h1.setX(qBound(p1.p.x(), p2.h1.x(), p2.p.x()));
+        m_points[i] = p1;
+        m_points[i+1] = p2;
+    }
+}
+
+void CubicBezierSpline::keepSorted()
+{
+    qSort(m_points.begin(), m_points.end(), pointLessThan);
+}
+
+QPointF CubicBezierSpline::point(double t, const QList< QPointF >& points)
+{
+    // coefficients from Bernstein basis polynomial of degree 3
+    double c1 = (1-t) * (1-t) * (1-t);
+    double c2 = 3 * t * (1-t) * (1-t);
+    double c3 = 3 * t * t * (1-t);
+    double c4 = t * t * t;
+    
+    return QPointF(points[0].x()*c1 + points[1].x()*c2 + points[2].x()*c3 + points[3].x()*c4,
+                   points[0].y()*c1 + points[1].y()*c2 + points[2].y()*c3 + points[3].y()*c4);
+}
+
+void CubicBezierSpline::update()
+{
+    if (m_validSpline)
+        return;
+
+    m_validSpline = true;
+    m_spline.clear();
+
+    QList <QPointF> points;
+    QPointF p;
+    for (int i = 0; i < m_points.count() - 1; ++i) {
+        points.clear();
+        points << m_points.at(i).p
+                << m_points.at(i).h2
+                << m_points.at(i+1).h1
+                << m_points.at(i+1).p;
+
+        int numberOfValues = (int)((points[3].x() - points[0].x()) * m_precision);
+        if (numberOfValues == 0)
+            numberOfValues = 1;
+        double step = 1 / (double)numberOfValues;
+        double t = 0;
+        while (t <= 1) {
+            p = point(t, points);
+            m_spline.insert(p.x(), p.y());
+            t += step;
+        }
+    }
+    /*QMap<double, double>::const_iterator i = m_spline.constBegin();
+    kDebug()<<"////////////spline/////////////";
+    while (i != m_spline.constEnd()) {
+        kDebug() << i.key() << i.value();
+        ++i;
+    }
+    kDebug()<<"////////////spline/////////////end";*/
+}
+
+#include "cubicbezierspline.moc"
diff --git a/src/beziercurve/cubicbezierspline.h b/src/beziercurve/cubicbezierspline.h
new file mode 100644 (file)
index 0000000..078066e
--- /dev/null
@@ -0,0 +1,70 @@
+/***************************************************************************
+ *   Copyright (C) 2010 by Till Theato (root@ttill.de)                     *
+ *   This file is part of Kdenlive (www.kdenlive.org).                     *
+ *                                                                         *
+ *   Kdenlive is free software: you can redistribute it and/or modify      *
+ *   it under the terms of the GNU General Public License as published by  *
+ *   the Free Software Foundation, either version 2 of the License, or     *
+ *   (at your option) any later version.                                   *
+ *                                                                         *
+ *   Kdenlive is distributed in the hope that it will be useful,           *
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
+ *   GNU General Public License for more details.                          *
+ *                                                                         *
+ *   You should have received a copy of the GNU General Public License     *
+ *   along with Kdenlive.  If not, see <http://www.gnu.org/licenses/>.     *
+ ***************************************************************************/
+
+#ifndef CUBICBEZIERSPLINE_H
+#define CUBICBEZIERSPLINE_H
+
+
+#include <QtCore>
+
+class BPoint
+{
+public:
+    QPointF h1;     // handle 1
+    QPointF p;      // point
+    QPointF h2;     // handle 2
+
+    bool operator==(const BPoint &point) const { return point.h1 == h1 && point.p == p && point.h2 == h2; }
+};
+
+class CubicBezierSpline : public QObject
+{
+    Q_OBJECT
+
+public:
+    CubicBezierSpline(QObject* parent = 0);
+    CubicBezierSpline(const CubicBezierSpline &spline, QObject* parent = 0);
+    CubicBezierSpline& operator=(const CubicBezierSpline &spline);
+
+    void fromString(const QString &spline);
+    QString toString() const;
+
+    QList <BPoint> points();
+    qreal value(qreal x, bool cont = false);
+
+    int setPoint(int ix, const BPoint &point);
+    int addPoint(const BPoint &point);
+    void removePoint(int ix);
+    void setPrecision(int pre);
+    int getPrecision();
+
+private:
+    QPointF point(double t, const QList<QPointF> &points);
+    void validatePoints();
+    void keepSorted();
+    void update();
+
+    QList <BPoint> m_points;
+    QMap <double, double> m_spline;
+    QMap <double, double>::const_iterator m_i;
+    bool m_validSpline;
+    int m_precision;
+    
+};
+
+#endif
index f3184f298b20248bbd4e38b54a909b9ccdde7f4d..f2ce5b1ed32c904bbfab0200ccb9862672da29d4 100644 (file)
@@ -34,6 +34,8 @@
 #include "colortools.h"
 #include "doubleparameterwidget.h"
 #include "cornerswidget.h"
+#include "beziercurve/beziersplinewidget.h"
+#include "beziercurve/cubicbezierspline.h"
 
 #include <KDebug>
 #include <KLocale>
@@ -379,6 +381,14 @@ void EffectStackEdit::transferParamDesc(const QDomElement d, int pos, int in, in
             QString depends = pa.attribute("depends");
             if (!depends.isEmpty())
                 meetDependency(paramName, type, EffectsList::parameter(e, depends));
+        } else if (type == "bezier_spline") {
+            BezierSplineWidget *widget = new BezierSplineWidget(this);
+            CubicBezierSpline spline;
+            spline.fromString(value);
+            widget->setSpline(spline);
+            m_vbox->addWidget(widget);
+            m_valueItems[paramName] = widget;
+            connect(widget, SIGNAL(modified()), this, SLOT(collectAllParameters()));
         } else if (type == "corners") {
             CornersWidget *corners = new CornersWidget(m_monitor, pos, isEffect, pa.attribute("factor").toInt(), this);
             connect(corners, SIGNAL(checkMonitorPosition(int)), this, SIGNAL(checkMonitorPosition(int)));
@@ -658,6 +668,9 @@ void EffectStackEdit::collectAllParameters()
             QString depends = pa.attributes().namedItem("depends").nodeValue();
             if (!depends.isEmpty())
                 meetDependency(paramName, type, EffectsList::parameter(newparam, depends));
+        } else if (type == "bezier_spline") {
+            BezierSplineWidget *widget = (BezierSplineWidget*)m_valueItems.value(paramName);
+            setValue = widget->spline().toString();
         } else if (type == "corners") {
             CornersWidget *corners = ((CornersWidget*)m_valueItems.value(paramName));
             QString xName = pa.attributes().namedItem("xpoints").nodeValue();