]> git.sesse.net Git - kdenlive/commitdiff
Vectorscope changes:
authorSimon A. Eugster <simon.eu@gmail.com>
Thu, 15 Jul 2010 20:14:23 +0000 (20:14 +0000)
committerSimon A. Eugster <simon.eu@gmail.com>
Thu, 15 Jul 2010 20:14:23 +0000 (20:14 +0000)
* Background color wheel can be switched on (YUV and modified YUV)
* Added black as paint mode
* Added YUV as paint mode
* Made default paint mode depend on selected background
* Colors for circle around mouse improved
* Labels for Widget added, layout adjusted

svn path=/trunk/kdenlive/; revision=4582

src/vectorscope.cpp
src/vectorscope.h
src/widgets/vectorscope_ui.ui

index 56124141781ad5ceb01d47a3b62b7fa77a44fcd9..84c061e91adeebd55563fe127adb546e1d629134 100644 (file)
@@ -43,6 +43,8 @@ mRgb2Yuv =                       r =
 #include <QMouseEvent>
 #include <QPainter>
 #include <QDebug>
+//#include <QMenu>
+#include <QAction>
 
 #include <qtconcurrentrun.h>
 #include <QThread>
@@ -52,6 +54,8 @@ mRgb2Yuv =                       r =
 
 const float SCALING = 1/.7; // See class docs
 const float P75 = .75;
+const unsigned char DEFAULT_Y = 255;
+
 const QPointF YUV_R(-.147,  .615);
 const QPointF YUV_G(-.289, -.515);
 const QPointF YUV_B(.437, -.100);
@@ -61,7 +65,8 @@ const QPointF YUV_Yl(-.437,  .100);
 
 const QPen penThick(QBrush(QColor(250,250,250)), 2, Qt::SolidLine);
 const QPen penThin(QBrush(QColor(250,250,250)), 1, Qt::SolidLine);
-const QPen penLight(QBrush(QColor(144,255,100,50)), 1, Qt::SolidLine);
+const QPen penLight(QBrush(QColor(200,200,250,150)), 1, Qt::SolidLine);
+const QPen penDark(QBrush(QColor(0,0,20,250)), 1, Qt::SolidLine);
 
 
 Vectorscope::Vectorscope(Monitor *projMonitor, Monitor *clipMonitor, QWidget *parent) :
@@ -69,33 +74,128 @@ Vectorscope::Vectorscope(Monitor *projMonitor, Monitor *clipMonitor, QWidget *pa
     m_projMonitor(projMonitor),
     m_clipMonitor(clipMonitor),
     m_activeRender(clipMonitor->render),
-    scaling(1),
+    m_scaling(1),
     circleEnabled(false),
     initialDimensionUpdateDone(false)
 {
     setupUi(this);
 
-    paintMode->insertItem(GREEN, i18n("Green"));
-    paintMode->insertItem(CHROMA, i18n("Chroma"));
-    paintMode->insertItem(ORIG, i18n("Original Color"));
+    // Use item index 20 to always append it to the list. (Update if more than 20 items here.)
+    paintMode->insertItem(20, i18n("Green"), QVariant(PAINT_GREEN));
+    paintMode->insertItem(20, i18n("Black"), QVariant(PAINT_BLACK));
+    paintMode->insertItem(20, i18n("Chroma"), QVariant(PAINT_CHROMA));
+    paintMode->insertItem(20, i18n("YUV"), QVariant(PAINT_YUV));
+    paintMode->insertItem(20, i18n("Original Color"), QVariant(PAINT_ORIG));
+
+    backgroundMode->insertItem(20, i18n("None"), QVariant(BG_NONE));
+    backgroundMode->insertItem(20, i18n("YUV"), QVariant(BG_YUV));
+    backgroundMode->insertItem(20, i18n("Chroma"), QVariant(BG_CHROMA));
+
+    cbAutoRefresh->setChecked(true);
 
     connect(paintMode, SIGNAL(currentIndexChanged(int)), this, SLOT(slotPaintModeChanged(int)));
+    connect(backgroundMode, SIGNAL(currentIndexChanged(int)), this, SLOT(slotBackgroundChanged()));
     connect(cbMagnify, SIGNAL(stateChanged(int)), this, SLOT(slotMagnifyChanged()));
     connect(this, SIGNAL(signalScopeCalculationFinished()), this, SLOT(slotScopeCalculationFinished()));
+    connect(this, SIGNAL(signalWheelCalculationFinished()), this, SLOT(slotWheelCalculationFinished()));
     connect(paintMode, SIGNAL(currentIndexChanged(int)), this, SLOT(slotUpdateScope()));
     connect(cbAutoRefresh, SIGNAL(stateChanged(int)), this, SLOT(slotUpdateScope()));
 
     newFrames.fetchAndStoreRelaxed(0);
     newChanges.fetchAndStoreRelaxed(0);
+    newWheelChanges.fetchAndStoreRelaxed(0);
+
+    setContextMenuPolicy(Qt::ActionsContextMenu);
+    m_aExportBackground = new QAction(i18n("Export background"), this);
+    m_aExportBackground->setEnabled(false);
+    addAction(m_aExportBackground);
+    connect(m_aExportBackground, SIGNAL(triggered()), this, SLOT(slotExportBackground()));
+
+//    m_contextMenu = QMenu(this, "Vectorscope menu");
+
 
     this->setMouseTracking(true);
     updateDimensions();
+    prodWheelThread();
 }
 
 Vectorscope::~Vectorscope()
 {
 }
 
+QImage Vectorscope::yuvColorWheel(const QSize &size, unsigned char Y, float scaling, bool modifiedVersion, bool circleOnly)
+{
+    QImage wheel(size, QImage::Format_ARGB32);
+    if (size.width() == 0 || size.height() == 0) {
+        qCritical("ERROR: Size of the color wheel must not be 0!");
+        return wheel;
+    }
+    wheel.fill(qRgba(0,0,0,0));
+
+    double dr, dg, db, du, dv, dmax;
+    double ru, rv, rr;
+    const int w = size.width();
+    const int h = size.height();
+    const float w2 = (float)w/2;
+    const float h2 = (float)h/2;
+
+    for (int u = 0; u < w; u++) {
+        // Transform u from {0,...,w} to [-1,1]
+        du = (double) 2*u/(w-1) - 1;
+        du = scaling*du;
+
+        for (int v = 0; v < h; v++) {
+            dv = (double) 2*v/(h-1) - 1;
+            dv = scaling*dv;
+
+            if (circleOnly) {
+                // Ellipsis equation: x²/a² + y²/b² = 1
+                // Here: x=ru, y=rv, a=w/2, b=h/2, 1=rr
+                // For rr > 1, the point lies outside. Don't draw it.
+                ru = u - w2;
+                rv = v - h2;
+                rr = ru*ru/(w2*w2) + rv*rv/(h2*h2);
+                if (rr > 1) {
+                    continue;
+                }
+            }
+
+            // Calculate the RGB values from YUV
+            dr = Y + 290.8*dv;
+            dg = Y - 100.6*du - 148*dv;
+            db = Y + 517.2*du;
+
+            if (modifiedVersion) {
+                // Scale the RGB values down, or up, to max 255
+                dmax = fabs(dr);
+                if (fabs(dg) > dmax) dmax = fabs(dg);
+                if (fabs(db) > dmax) dmax = fabs(db);
+                dmax = 255/dmax;
+
+                dr *= dmax;
+                dg *= dmax;
+                db *= dmax;
+            }
+
+            // Avoid overflows (which would generate intersting patterns).
+            // Note that not all possible (y,u,v) values with u,v \in [-1,1]
+            // have a correct RGB representation, therefore some RGB values
+            // may exceed {0,...,255}.
+            if (dr < 0) dr = 0;
+            if (dg < 0) dg = 0;
+            if (db < 0) db = 0;
+            if (dr > 255) dr = 255;
+            if (dg > 255) dg = 255;
+            if (db > 255) db = 255;
+
+            wheel.setPixel(u, (h-v-1), qRgba(dr, dg, db, 255));
+        }
+    }
+
+    emit signalWheelCalculationFinished();
+    return wheel;
+}
+
 /**
 
   Input point is on [-1,1]², 0 being at the center,
@@ -135,10 +235,39 @@ bool Vectorscope::prodCalcThread()
         // running member functions in a thread
         qDebug() << "Calc thread not running anymore, finished: " << m_scopeCalcThread.isFinished() << ", Starting new thread";
         m_scopeCalcThread = QtConcurrent::run(this, &Vectorscope::calculateScope);
+        newFrames.fetchAndStoreRelease(0); // Reset number of new frames, as we just got the newest
+        newChanges.fetchAndStoreRelease(0); // Do the same with the external changes counter
         return true;
     }
 }
 
+bool Vectorscope::prodWheelThread()
+{
+    if (m_wheelCalcThread.isRunning()) {
+        qDebug() << "Wheel thread still running.";
+        return false;
+    } else {
+        switch (backgroundMode->itemData(backgroundMode->currentIndex()).toInt()) {
+        case BG_NONE:
+            qDebug() << "No background.";
+            m_wheel = QImage();
+            this->update();
+            break;
+        case BG_YUV:
+            qDebug() << "YUV background.";
+            m_wheelCalcThread = QtConcurrent::run(this, &Vectorscope::yuvColorWheel, m_scopeRect.size(), (unsigned char) 128, 1/SCALING, false, true);
+            break;
+        case BG_CHROMA:
+            m_wheelCalcThread = QtConcurrent::run(this, &Vectorscope::yuvColorWheel, m_scopeRect.size(), (unsigned char) 255, 1/SCALING, true, true);
+            break;
+        }
+        newWheelChanges.fetchAndStoreRelaxed(0);
+        return true;
+    }
+}
+
+
+
 void Vectorscope::calculateScope()
 {
     // Prepare the vectorscope data
@@ -146,8 +275,6 @@ void Vectorscope::calculateScope()
     scope.fill(qRgba(0,0,0,0));
 
     QImage img(m_activeRender->extractFrame(m_activeRender->seekFramePosition()));
-    newFrames.fetchAndStoreRelease(0); // Reset number of new frames, as we just got the newest
-    newChanges.fetchAndStoreRelease(0); // Do the same with the external changes counter
     const uchar *bits = img.bits();
 
     int r,g,b;
@@ -169,7 +296,7 @@ void Vectorscope::calculateScope()
         u = (double) -0.0005781* r -0.001135 * g +0.001713 * b;
         v = (double)  0.002411 * r -0.002019 * g -0.0003921* b;
 
-        pt = mapToCanvas(scopeRect, QPointF(SCALING*scaling*u, SCALING*scaling*v));
+        pt = mapToCanvas(scopeRect, QPointF(SCALING*m_scaling*u, SCALING*m_scaling*v));
 
         if (!(pt.x() <= scopeRect.width() && pt.x() >= 0
             && pt.y() <= scopeRect.height() && pt.y() >= 0)) {
@@ -177,14 +304,36 @@ void Vectorscope::calculateScope()
 
         } else {
 
-            // Draw the pixel using the chosen draw mode
-            switch (paintMode->currentIndex()) {
-            case CHROMA:
-                dy = 200;
+            // Draw the pixel using the chosen draw mode.
+            switch (paintMode->itemData(paintMode->currentIndex()).toInt()) {
+            case PAINT_YUV:
+                // see yuvColorWheel
+                dy = 128; // Default Y value. Lower = darker.
+
+                // Calculate the RGB values from YUV
+                dr = dy + 290.8*v;
+                dg = dy - 100.6*u - 148*v;
+                db = dy + 517.2*u;
+
+                if (dr < 0) dr = 0;
+                if (dg < 0) dg = 0;
+                if (db < 0) db = 0;
+                if (dr > 255) dr = 255;
+                if (dg > 255) dg = 255;
+                if (db > 255) db = 255;
+
+                scope.setPixel(pt, qRgba(dr, dg, db, 255));
+                break;
+
+            case PAINT_CHROMA:
+                dy = 200; // Default Y value. Lower = darker.
+
+                // Calculate the RGB values from YUV
                 dr = dy + 290.8*v;
                 dg = dy - 100.6*u - 148*v;
                 db = dy + 517.2*u;
 
+                // Scale the RGB values back to max 255
                 dmax = dr;
                 if (dg > dmax) dmax = dg;
                 if (db > dmax) dmax = db;
@@ -196,13 +345,17 @@ void Vectorscope::calculateScope()
 
                 scope.setPixel(pt, qRgba(dr, dg, db, 255));
                 break;
-            case ORIG:
+            case PAINT_ORIG:
                 scope.setPixel(pt, *col);
                 break;
-            case GREEN:
+            case PAINT_GREEN:
                 px = scope.pixel(pt);
                 scope.setPixel(pt, qRgba(qRed(px)+(255-qRed(px))/40, 255, qBlue(px)+(255-qBlue(px))/40, qAlpha(px)+(255-qAlpha(px))/20));
                 break;
+            case PAINT_BLACK:
+                px = scope.pixel(pt);
+                scope.setPixel(pt, qRgba(0,0,0, qAlpha(px)+(255-qAlpha(px))/20));
+                break;
             }
         }
 
@@ -254,6 +407,8 @@ void Vectorscope::paintEvent(QPaintEvent *)
     davinci.setRenderHint(QPainter::Antialiasing, true);
     davinci.fillRect(0, 0, this->size().width(), this->size().height(), QColor(25,25,23));
 
+    davinci.drawImage(m_scopeRect.topLeft(), m_wheel);
+
     davinci.setPen(penThick);
     davinci.drawEllipse(m_scopeRect);
 
@@ -282,9 +437,29 @@ void Vectorscope::paintEvent(QPaintEvent *)
     davinci.drawEllipse(vinciPoint, 4,4);
     davinci.drawText(vinciPoint-QPoint(25, 0), "Yl");
 
+    switch (backgroundMode->itemData(backgroundMode->currentIndex()).toInt()) {
+    case BG_NONE:
+        davinci.setPen(penLight);
+        break;
+    default:
+        davinci.setPen(penDark);
+        break;
+    }
+
+    davinci.drawLine(mapToCanvas(m_scopeRect, QPointF(0,-.9)), mapToCanvas(m_scopeRect, QPointF(0,.9)));
+    davinci.drawLine(mapToCanvas(m_scopeRect, QPointF(-.9,0)), mapToCanvas(m_scopeRect, QPointF(.9,0)));
+
     // Draw RGB/CMY points with 75% chroma (for NTSC)
-    davinci.setPen(penThin);
+    switch (backgroundMode->itemData(backgroundMode->currentIndex()).toInt()) {
+    case BG_CHROMA:
+        davinci.setPen(penDark);
+        break;
+    default:
+        davinci.setPen(penThin);
+        break;
+    }
     davinci.drawEllipse(centerPoint, 5,5);
+    davinci.setPen(penThin);
     davinci.drawEllipse(mapToCanvas(m_scopeRect, P75*SCALING*YUV_R), 3,3);
     davinci.drawEllipse(mapToCanvas(m_scopeRect, P75*SCALING*YUV_G), 3,3);
     davinci.drawEllipse(mapToCanvas(m_scopeRect, P75*SCALING*YUV_B), 3,3);
@@ -307,9 +482,16 @@ void Vectorscope::paintEvent(QPaintEvent *)
         QPoint reference = mapToCanvas(m_scopeRect, QPointF(1,0));
 
         int r = sqrt(dx*dx + dy*dy);
-        float percent = (float) 100*r/SCALING/scaling/(reference.x() - centerPoint.x());
-
-        davinci.setPen(penLight);
+        float percent = (float) 100*r/SCALING/m_scaling/(reference.x() - centerPoint.x());
+
+        switch (backgroundMode->itemData(backgroundMode->currentIndex()).toInt()) {
+        case BG_NONE:
+            davinci.setPen(penLight);
+            break;
+        default:
+            davinci.setPen(penDark);
+            break;
+        }
         davinci.drawEllipse(centerPoint, r,r);
         davinci.setPen(penThin);
         davinci.drawText(m_scopeRect.bottomRight()-QPoint(40,0), QVariant((int)percent).toString().append(" %"));
@@ -326,9 +508,9 @@ void Vectorscope::paintEvent(QPaintEvent *)
 void Vectorscope::slotMagnifyChanged()
 {
     if (cbMagnify->isChecked()) {
-        scaling = 1.4;
+        m_scaling = 1.4;
     } else {
-        scaling = 1;
+        m_scaling = 1;
     }
     prodCalcThread();
 }
@@ -358,9 +540,6 @@ void Vectorscope::slotRenderZoneUpdated()
 
 void Vectorscope::slotScopeCalculationFinished()
 {
-    this->update();
-    qDebug() << "Scope updated.";
-
     if (!m_scopeCalcThread.isFinished()) {
         // Wait for the thread to finish. Otherwise the scope might not get updated
         // as prodCalcThread may see it still running.
@@ -370,6 +549,9 @@ void Vectorscope::slotScopeCalculationFinished()
         qDebug() << "Done. Waited for " << start.msecsTo(QTime::currentTime()) << " ms";
     }
 
+    this->update();
+    qDebug() << "Scope updated.";
+
     // If auto-refresh is enabled and new frames are available,
     // just start the next calculation.
     if (newFrames > 0 && cbAutoRefresh->isChecked()) {
@@ -383,11 +565,64 @@ void Vectorscope::slotScopeCalculationFinished()
     }
 }
 
+void Vectorscope::slotWheelCalculationFinished()
+{
+    if (!m_wheelCalcThread.isFinished()) {
+        QTime start = QTime::currentTime();
+        qDebug() << "Wheel calc has not finished yet, waiting ...";
+        m_wheelCalcThread.waitForFinished();
+        qDebug() << "Done. Waited for " << start.msecsTo(QTime::currentTime()) << " ms";
+    }
+
+    qDebug() << "Wheel calculated. Updating.";
+    qDebug() << m_wheelCalcThread.resultCount() << " results from the Wheel thread.";
+    if (m_wheelCalcThread.resultCount() > 0) {
+        m_wheel = m_wheelCalcThread.resultAt(0);
+    }
+    this->update();
+    if (newWheelChanges > 0) {
+        prodWheelThread();
+    }
+}
+
 void Vectorscope::slotUpdateScope()
 {
     prodCalcThread();
 }
 
+void Vectorscope::slotUpdateWheel()
+{
+    prodWheelThread();
+}
+
+void Vectorscope::slotExportBackground()
+{
+    qDebug() << "Exporting background";
+}
+
+void Vectorscope::slotBackgroundChanged()
+{
+    // Background changed, switch to a suitable color mode now
+    int index;
+    switch (backgroundMode->itemData(backgroundMode->currentIndex()).toInt()) {
+    case BG_YUV:
+        index = paintMode->findData(QVariant(PAINT_BLACK));
+        if (index >= 0) {
+            paintMode->setCurrentIndex(index);
+        }
+        break;
+
+    case BG_NONE:
+        if (paintMode->itemData(paintMode->currentIndex()) == PAINT_BLACK) {
+            index = paintMode->findData(QVariant(PAINT_GREEN));
+            paintMode->setCurrentIndex(index);
+        }
+        break;
+    }
+    newWheelChanges.fetchAndAddAcquire(1);
+    prodWheelThread();
+}
+
 
 
 ///// Events /////
index e57bf36b8d62904de51c9e0091687e99b5313bee..9e5ad75e7c3c9bcbf6d1a9db63444dfa70ada31a 100644 (file)
@@ -20,7 +20,8 @@ class Render;
 class Monitor;
 class Vectorscope_UI;
 
-enum PAINT_MODE { GREEN = 0, ORIG = 1, CHROMA = 2 };
+enum PAINT_MODE { PAINT_GREEN = 0, PAINT_ORIG = 1, PAINT_CHROMA = 2, PAINT_YUV = 3, PAINT_BLACK = 4 };
+enum BACKGROUND_MODE { BG_NONE = 0, BG_YUV = 1, BG_CHROMA = 2 };
 
 class Vectorscope : public QWidget, public Ui::Vectorscope_UI {
     Q_OBJECT
@@ -28,6 +29,7 @@ class Vectorscope : public QWidget, public Ui::Vectorscope_UI {
 public:
     Vectorscope(Monitor *projMonitor, Monitor *clipMonitor, QWidget *parent = 0);
     ~Vectorscope();
+    QImage yuvColorWheel(const QSize& size, const unsigned char Y, const float scaling, const bool modifiedVersion, const bool circleOnly);
 
 protected:
     void paintEvent(QPaintEvent *);
@@ -40,10 +42,13 @@ private:
     Monitor *m_clipMonitor;
     Render *m_activeRender;
 
+//    QMenu m_contextMenu;
+    QAction *m_aExportBackground;
+
     /** How to represent the pixels on the scope (green, original color, ...) */
     int iPaintMode;
 
-    float scaling;
+    float m_scaling;
 
 
     QPoint mapToCanvas(QRect inside, QPointF point);
@@ -61,14 +66,17 @@ private:
     /** The vectorscope color distribution.
         Class variable as calculated by a thread. */
     QImage m_scope;
+    QImage m_wheel;
 
     void calculateScope();
     QFuture<void> m_scopeCalcThread;
+    QFuture<QImage> m_wheelCalcThread;
 
     /** Prods the Scope calculation thread. If it is running, do nothing. If it is not,
       run a new thread.
       Returns true if a new thread has been started. */
     bool prodCalcThread();
+    bool prodWheelThread();
 
     /** Counts the number of frames that have been rendered in one of the monitors.
       The frame number will be reset when the vectorscope starts calculating the
@@ -79,17 +87,23 @@ private:
       are generated, but the scope has to be updated in any case (also if auto-update
       is not enabled). */
     QAtomicInt newChanges;
+    /** Counts the number of changes concerning the background wheel */
+    QAtomicInt newWheelChanges;
 
 signals:
     void signalScopeCalculationFinished();
+    void signalWheelCalculationFinished();
 
 private slots:
     void slotMagnifyChanged();
+    void slotBackgroundChanged();
     void slotActiveMonitorChanged(bool isClipMonitor);
     void slotRenderZoneUpdated();
     void slotScopeCalculationFinished();
+    void slotWheelCalculationFinished();
     void slotUpdateScope();
-
+    void slotUpdateWheel();
+    void slotExportBackground();
 };
 
 #endif // VECTORSCOPE_H
index 17070b6e86b07684fd8e922f73269a916c1a35c6..1039c591d817855fa8c8571655aedcc271bba697 100644 (file)
      <x>0</x>
      <y>0</y>
      <width>401</width>
-     <height>68</height>
+     <height>101</height>
     </rect>
    </property>
    <layout class="QGridLayout" name="gridLayout">
     <property name="sizeConstraint">
      <enum>QLayout::SetDefaultConstraint</enum>
     </property>
+    <property name="leftMargin">
+     <number>5</number>
+    </property>
+    <property name="topMargin">
+     <number>5</number>
+    </property>
+    <property name="rightMargin">
+     <number>5</number>
+    </property>
     <item row="1" column="0">
      <layout class="QVBoxLayout" name="controlsArea">
       <item>
-       <widget class="QComboBox" name="paintMode"/>
+       <layout class="QGridLayout" name="gridLayout_2">
+        <property name="horizontalSpacing">
+         <number>10</number>
+        </property>
+        <item row="1" column="1">
+         <widget class="QComboBox" name="paintMode">
+          <property name="sizePolicy">
+           <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+            <horstretch>0</horstretch>
+            <verstretch>0</verstretch>
+           </sizepolicy>
+          </property>
+         </widget>
+        </item>
+        <item row="2" column="1">
+         <widget class="QComboBox" name="backgroundMode">
+          <property name="sizePolicy">
+           <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+            <horstretch>0</horstretch>
+            <verstretch>0</verstretch>
+           </sizepolicy>
+          </property>
+         </widget>
+        </item>
+        <item row="1" column="0">
+         <widget class="QLabel" name="lblPaintMode">
+          <property name="text">
+           <string>Paint mode</string>
+          </property>
+         </widget>
+        </item>
+        <item row="2" column="0">
+         <widget class="QLabel" name="lblBackground">
+          <property name="text">
+           <string>Background</string>
+          </property>
+         </widget>
+        </item>
+       </layout>
       </item>
       <item>
        <layout class="QHBoxLayout" name="horizontalLayout">