]> git.sesse.net Git - kdenlive/commitdiff
Vectorscope changes:
authorSimon A. Eugster <simon.eu@gmail.com>
Tue, 20 Jul 2010 20:48:29 +0000 (20:48 +0000)
committerSimon A. Eugster <simon.eu@gmail.com>
Tue, 20 Jul 2010 20:48:29 +0000 (20:48 +0000)
* Magnification = Gain can now be changed with a slider in 0.1 steps up to 4x
* Vectorscope generator extracted to separate (re-usable) class
* Inherits from AbstractScopeWidget, which means
  - Lots of duplicate code removed
  - Realtime bug that led to crash fixed (I hope)
* Checks whether events have been connected successfully with Q_ASSERT
* Delete members in the destructor
* .ui file: Palette removed, unnecessary grid layout removed as well
AbstractScopeWidget changes:
* Processing HUD and Background as well now.
* Re-render when auto refresh is selected
* Don't recalculate if the scope is hidden behind another widget
* Slot added to force the update of a certain layer
* Update scope when risen to top of a widget stack
* Realtime acceleration factor can now be calculated by the implementing widget
* Acceleration factor is protected instead of private now so that they can be accessed by other render tasks (hud displays scope factor e.g.)
WaveformGenerator changes:
* Using subclassing instead of direct #includes in the .h file («good style» :P)

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

src/CMakeLists.txt
src/abstractscopewidget.cpp
src/abstractscopewidget.h
src/colorcorrection/vectorscopegenerator.cpp [new file with mode: 0644]
src/colorcorrection/vectorscopegenerator.h [new file with mode: 0644]
src/colorcorrection/waveformgenerator.cpp
src/colorcorrection/waveformgenerator.h
src/vectorscope.cpp
src/vectorscope.h
src/widgets/vectorscope_ui.ui

index efe2e323c42d28249be729c56d8785b82e6c0009..143345504962b02af35be05fee94561e9217bc0f 100644 (file)
@@ -202,6 +202,7 @@ set(kdenlive_SRCS
   rebuildgroupcommand.cpp
   waveform.cpp
   colorcorrection/waveformgenerator.cpp
+  colorcorrection/vectorscopegenerator.cpp
   testwidget.cpp
   razorgroupcommand.cpp
 )
index 62958982ca824866189eb98b49f67ee269485b97..a4a2a71963fcfc0b6966f929f66bf3af50b5b5c9 100644 (file)
@@ -31,12 +31,12 @@ AbstractScopeWidget::AbstractScopeWidget(Monitor *projMonitor, Monitor *clipMoni
     m_projMonitor(projMonitor),
     m_clipMonitor(clipMonitor),
     offset(5),
-    m_semaphoreHUD(1),
-    m_semaphoreScope(1),
-    m_semaphoreBackground(1),
     m_accelFactorHUD(1),
     m_accelFactorScope(1),
     m_accelFactorBackground(1),
+    m_semaphoreHUD(1),
+    m_semaphoreScope(1),
+    m_semaphoreBackground(1),
     initialDimensionUpdateDone(false)
 
 {
@@ -77,6 +77,7 @@ AbstractScopeWidget::AbstractScopeWidget(Monitor *projMonitor, Monitor *clipMoni
     b &= connect(this, SIGNAL(signalScopeRenderingFinished(uint,uint)), this, SLOT(slotScopeRenderingFinished(uint,uint)));
     b &= connect(this, SIGNAL(signalBackgroundRenderingFinished(uint,uint)), this, SLOT(slotBackgroundRenderingFinished(uint,uint)));
     b &= connect(m_aRealtime, SIGNAL(toggled(bool)), this, SLOT(slotResetRealtimeFactor(bool)));
+    b &= connect(m_aAutoRefresh, SIGNAL(toggled(bool)), this, SLOT(slotAutoRefreshToggled(bool)));
 
     Q_ASSERT(b);
 }
@@ -85,12 +86,34 @@ AbstractScopeWidget::~AbstractScopeWidget()
 {
     delete m_menu;
     delete m_aAutoRefresh;
+    delete m_aRealtime;
 }
 
-void AbstractScopeWidget::prodHUDThread() {}
+void AbstractScopeWidget::prodHUDThread()
+{
+    if (this->visibleRegion().isEmpty()) {
+        qDebug() << "Scope " << widgetName() << " is not visible. Not calculating HUD.";
+    }
+    if (m_semaphoreHUD.tryAcquire(1)) {
+        Q_ASSERT(!m_threadHUD.isRunning());
+
+        m_newHUDFrames.fetchAndStoreRelaxed(0);
+        m_newHUDUpdates.fetchAndStoreRelaxed(0);
+        m_threadHUD = QtConcurrent::run(this, &AbstractScopeWidget::renderHUD, m_accelFactorHUD);
+        qDebug() << "HUD thread started in " << widgetName();
+
+    } else {
+        qDebug() << "HUD semaphore locked, not prodding in " << widgetName() << ". Thread running: " << m_threadHUD.isRunning();
+    }
+}
 
 void AbstractScopeWidget::prodScopeThread()
 {
+    // Only start a new thread if the scope is actually visible
+    // and not hidden by another widget on the stack.
+    if (this->visibleRegion().isEmpty()) {
+        qDebug() << "Scope " << widgetName() << " is not visible. Not calculating scope.";
+    }
     // Try to acquire the semaphore. This must only succeed if m_threadScope is not running
     // anymore. Therefore the semaphore must NOT be released before m_threadScope ends.
     // If acquiring the semaphore fails, the thread is still running.
@@ -99,15 +122,62 @@ void AbstractScopeWidget::prodScopeThread()
 
         m_newScopeFrames.fetchAndStoreRelaxed(0);
         m_newScopeUpdates.fetchAndStoreRelaxed(0);
+
+        // See http://doc.qt.nokia.com/latest/qtconcurrentrun.html#run about
+        // running member functions in a thread
         m_threadScope = QtConcurrent::run(this, &AbstractScopeWidget::renderScope, m_accelFactorScope);
+
         qDebug() << "Scope thread started in " << widgetName();
 
     } else {
         qDebug() << "Scope semaphore locked, not prodding in " << widgetName() << ". Thread running: " << m_threadScope.isRunning();
     }
 }
+void AbstractScopeWidget::prodBackgroundThread()
+{
+    if (this->visibleRegion().isEmpty()) {
+        qDebug() << "Scope " << widgetName() << " is not visible. Not calculating background.";
+    }
+    if (m_semaphoreBackground.tryAcquire(1)) {
+        Q_ASSERT(!m_threadBackground.isRunning());
+
+        m_newBackgroundFrames.fetchAndStoreRelaxed(0);
+        m_newBackgroundUpdates.fetchAndStoreRelaxed(0);
+        m_threadBackground = QtConcurrent::run(this, &AbstractScopeWidget::renderBackground, m_accelFactorBackground);
+        qDebug() << "Background thread started in " << widgetName();
+
+    } else {
+        qDebug() << "Background semaphore locked, not prodding in " << widgetName() << ". Thread running: " << m_threadBackground.isRunning();
+    }
+}
+
+void AbstractScopeWidget::forceUpdate()
+{
+    m_newHUDUpdates.fetchAndAddRelaxed(1);
+    m_newScopeUpdates.fetchAndAddRelaxed(1);
+    m_newBackgroundUpdates.fetchAndAddRelaxed(1);
+    prodHUDThread();
+    prodScopeThread();
+    prodBackgroundThread();
+}
+void AbstractScopeWidget::forceUpdateHUD()
+{
+    m_newHUDUpdates.fetchAndAddRelaxed(1);
+    prodHUDThread();
+
+}
+void AbstractScopeWidget::forceUpdateScope()
+{
+    m_newScopeUpdates.fetchAndAddRelaxed(1);
+    prodScopeThread();
+
+}
+void AbstractScopeWidget::forceUpdateBackground()
+{
+    m_newBackgroundUpdates.fetchAndAddRelaxed(1);
+    prodBackgroundThread();
 
-void AbstractScopeWidget::prodBackgroundThread() {}
+}
 
 
 ///// Events /////
@@ -125,17 +195,19 @@ void AbstractScopeWidget::resizeEvent(QResizeEvent *event)
     // Update the dimension of the available rect for painting
     m_scopeRect = scopeRect();
 
-    m_newHUDUpdates.fetchAndAddRelaxed(1);
-    m_newScopeUpdates.fetchAndAddRelaxed(1);
-    m_newBackgroundUpdates.fetchAndAddRelaxed(1);
-
-    prodHUDThread();
-    prodScopeThread();
-    prodBackgroundThread();
+    forceUpdate();
 
     QWidget::resizeEvent(event);
 }
 
+void AbstractScopeWidget::raise()
+{
+    // Widget has been brought to the top of a widget stack,
+    // layers need to be updated.
+    QWidget::raise();
+    forceUpdate();
+}
+
 void AbstractScopeWidget::paintEvent(QPaintEvent *)
 {
 //    qDebug() << "Painting now on scope " << widgetName();
@@ -152,7 +224,6 @@ void AbstractScopeWidget::paintEvent(QPaintEvent *)
     davinci.drawImage(scopeRect().topLeft(), m_imgBackground);
     davinci.drawImage(scopeRect().topLeft(), m_imgScope);
     davinci.drawImage(scopeRect().topLeft(), m_imgHUD);
-    davinci.fillRect(scopeRect(), QBrush(QColor(200, 100, 0, 16)));
 }
 
 void AbstractScopeWidget::customContextMenuRequested(const QPoint &pos)
@@ -160,12 +231,39 @@ void AbstractScopeWidget::customContextMenuRequested(const QPoint &pos)
     m_menu->exec(this->mapToGlobal(pos));
 }
 
+uint AbstractScopeWidget::calculateAccelFactorHUD(uint oldMseconds, uint) { return ceil((float)oldMseconds*REALTIME_FPS/1000 ); }
+uint AbstractScopeWidget::calculateAccelFactorScope(uint oldMseconds, uint) { return ceil((float)oldMseconds*REALTIME_FPS/1000 ); }
+uint AbstractScopeWidget::calculateAccelFactorBackground(uint oldMseconds, uint) { return ceil((float)oldMseconds*REALTIME_FPS/1000 ); }
+
 
 ///// Slots /////
 
-void AbstractScopeWidget::slotHUDRenderingFinished(uint, uint) {}
+void AbstractScopeWidget::slotHUDRenderingFinished(uint mseconds, uint oldFactor)
+{
+    qDebug() << "HUD rendering has finished, waiting for termination in " << widgetName();
+    m_threadHUD.waitForFinished();
+    m_imgHUD = m_threadHUD.result();
+
+    m_semaphoreHUD.release(1);
+    this->update();
+
+    int accel;
+    if (m_aRealtime->isChecked()) {
+        accel = calculateAccelFactorHUD(mseconds, oldFactor);
+        if (m_accelFactorHUD < 1) {
+            accel = 1;
+        }
+        m_accelFactorHUD = accel;
+    }
+
+    if ( (m_newHUDFrames > 0 && m_aAutoRefresh->isChecked()) || m_newHUDUpdates > 0) {
+        qDebug() << "Trying to start a new HUD thread for " << widgetName()
+                << ". New frames/updates: " << m_newHUDFrames << "/" << m_newHUDUpdates;
+        prodHUDThread();;
+    }
+}
 
-void AbstractScopeWidget::slotScopeRenderingFinished(uint mseconds, uint)
+void AbstractScopeWidget::slotScopeRenderingFinished(uint mseconds, uint oldFactor)
 {
     // The signal can be received before the thread has really finished. So we
     // need to wait until it has really finished before starting a new thread.
@@ -181,9 +279,9 @@ void AbstractScopeWidget::slotScopeRenderingFinished(uint mseconds, uint)
     // Calculate the acceleration factor hint to get «realtime» updates.
     int accel;
     if (m_aRealtime->isChecked()) {
-        accel = ceil((float)mseconds*REALTIME_FPS/1000 );
+        accel = calculateAccelFactorScope(mseconds, oldFactor);
         if (m_accelFactorScope < 1) {
-            // If mseconds is 0.
+            // If mseconds happens to be 0.
             accel = 1;
         }
         // Don't directly calculate with m_accelFactorScope as we are dealing with concurrency.
@@ -193,13 +291,36 @@ void AbstractScopeWidget::slotScopeRenderingFinished(uint mseconds, uint)
     }
 
     if ( (m_newScopeFrames > 0 && m_aAutoRefresh->isChecked()) || m_newScopeUpdates > 0) {
-        qDebug() << "Trying to start a new thread for " << widgetName()
+        qDebug() << "Trying to start a new scope thread for " << widgetName()
                 << ". New frames/updates: " << m_newScopeFrames << "/" << m_newScopeUpdates;
         prodScopeThread();
     }
 }
 
-void AbstractScopeWidget::slotBackgroundRenderingFinished(uint, uint) {}
+void AbstractScopeWidget::slotBackgroundRenderingFinished(uint mseconds, uint oldFactor)
+{
+    qDebug() << "Background rendering has finished, waiting for termination in " << widgetName();
+    m_threadBackground.waitForFinished();
+    m_imgBackground = m_threadBackground.result();
+
+    m_semaphoreBackground.release(1);
+    this->update();
+
+    int accel;
+    if (m_aRealtime->isChecked()) {
+        accel = calculateAccelFactorBackground(mseconds, oldFactor);
+        if (m_accelFactorBackground < 1) {
+            accel = 1;
+        }
+        m_accelFactorBackground = accel;
+    }
+
+    if ( (m_newBackgroundFrames > 0 && m_aAutoRefresh->isChecked()) || m_newBackgroundUpdates > 0) {
+        qDebug() << "Trying to start a new background thread for " << widgetName()
+                << ". New frames/updates: " << m_newBackgroundFrames << "/" << m_newBackgroundUpdates;
+        prodBackgroundThread();;
+    }
+}
 
 void AbstractScopeWidget::slotActiveMonitorChanged(bool isClipMonitor)
 {
@@ -247,3 +368,11 @@ void AbstractScopeWidget::slotResetRealtimeFactor(bool realtimeChecked)
         m_accelFactorBackground = 1;
     }
 }
+
+void AbstractScopeWidget::slotAutoRefreshToggled(bool autoRefresh)
+{
+    // TODO only if depends on input
+    if (autoRefresh) {
+        forceUpdate();
+    }
+}
index 67c76c9e4be27cddb7dc47aa71b3b7c4972e886c..65cde13823d270127b8743d61ceb5a0c885a1a9a 100644 (file)
@@ -68,6 +68,8 @@ public:
     virtual ~AbstractScopeWidget(); // Must be virtual because of inheritance, to avoid memory leaks
     QPalette m_scopePalette;
 
+    ///// Unimplemented /////
+
     virtual QString widgetName() const = 0;
 
 protected:
@@ -103,10 +105,17 @@ protected:
     QImage m_imgScope;
     QImage m_imgBackground;
 
+    /** The acceleration factors can be accessed also by other renderer tasks,
+        e.g. to display the scope's acceleration factor in the HUD renderer. */
+    int m_accelFactorHUD;
+    int m_accelFactorScope;
+    int m_accelFactorBackground;
+
 
     ///// Unimplemented Methods /////
 
-    /** Where on the widget we can paint in */
+    /** Where on the widget we can paint in.
+        May also update other variables that depend on the widget's size.  */
     virtual QRect scopeRect() = 0;
 
     /** @brief HUD renderer. Must emit signalHUDRenderingFinished(). @see renderScope */
@@ -127,12 +136,28 @@ protected:
     /** @see isHUDDependingOnInput() */
     virtual bool isBackgroundDependingOnInput() const = 0;
 
-    ///// Methods /////
+    ///// Can be reimplemented /////
+    /** Calculates the acceleration factor to be used by the render thread.
+        This method can be refined in the subclass if required. */
+    virtual uint calculateAccelFactorHUD(uint oldMseconds, uint oldFactor);
+    virtual uint calculateAccelFactorScope(uint oldMseconds, uint oldFactor);
+    virtual uint calculateAccelFactorBackground(uint oldMseconds, uint oldFactor);
+
+    ///// Reimplemented /////
 
     void mouseReleaseEvent(QMouseEvent *);
     void paintEvent(QPaintEvent *);
+    void raise();
     void resizeEvent(QResizeEvent *);
 
+protected slots:
+    /** Forces an update of all layers. */
+    void forceUpdate();
+    void forceUpdateHUD();
+    void forceUpdateScope();
+    void forceUpdateBackground();
+    void slotAutoRefreshToggled(bool);
+
 signals:
     /** mseconds represent the time taken for the calculation,
         accelerationFactor the acceleration factor that has been used. */
@@ -165,10 +190,6 @@ private:
     QFuture<QImage> m_threadScope;
     QFuture<QImage> m_threadBackground;
 
-    int m_accelFactorHUD;
-    int m_accelFactorScope;
-    int m_accelFactorBackground;
-
     bool initialDimensionUpdateDone;
     void prodHUDThread();
     void prodScopeThread();
diff --git a/src/colorcorrection/vectorscopegenerator.cpp b/src/colorcorrection/vectorscopegenerator.cpp
new file mode 100644 (file)
index 0000000..07828c7
--- /dev/null
@@ -0,0 +1,196 @@
+/***************************************************************************
+ *   Copyright (C) 2010 by Simon Andreas Eugster (simon.eu@gmail.com)      *
+ *   This file is part of kdenlive. See www.kdenlive.org.                  *
+ *                                                                         *
+ *   This program 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.                                   *
+ ***************************************************************************/
+
+/**
+
+  Vectorscope.
+
+  The basis matrix for converting RGB to YUV is:
+
+mRgb2Yuv =                       r =
+
+   0.29900   0.58700   0.11400     1.00000
+  -0.14741  -0.28939   0.43680  x  0.00000
+   0.61478  -0.51480  -0.09998     0.00000
+
+  The resulting YUV value is then drawn on the circle
+  using U and V as coordinate values.
+
+  The maximum length of such an UV vector is reached
+  for the colors Red and Cyan: 0.632.
+  To make optimal use of space in the circle, this value
+  can be used for scaling.
+
+  As we are dealing with RGB values in a range of {0,...,255}
+  and the conversion values are made for [0,1], we already
+  divide the conversion values by 255 previously, e.g. in
+  GNU octave.
+
+  See also:
+    http://de.wikipedia.org/wiki/Vektorskop
+    http://www.elektroniktutor.de/techno/vektskop.html
+
+ */
+
+#include <math.h>
+#include <QDebug>
+#include <QImage>
+#include "vectorscopegenerator.h"
+
+// The maximum distance from the center for any RGB color is 0.63, so
+// no need to make the circle bigger than required.
+const float SCALING = 1/.7;
+
+const float VectorscopeGenerator::scaling = 1/.7;
+
+/**
+  Input point is on [-1,1]², 0 being at the center,
+  and positive directions are →top/→right.
+
+  Maps to the coordinates used in QImages with the 0 point
+  at the top left corner.
+
+ -1          +1
++1+-----------+
+  |    +      |
+  |  --0++    |
+  |    -      |
+-1+-----------+
+     vvv
+   mapped to
+      v
+  0      x
+ 0+------+
+  |0++   |
+  |-     |
+  |-     |
+ y+------+
+
+  With y:
+  1. Scale from [-1,1] to [0,1] with                    y01 := (y+1)/2
+  2. Invert (Orientation of the y axis changes) with    y10 := 1-y01
+  3. Scale from [1,0] to [height-1,0] with              yy  := (height-1) * y10
+  x does not need to be inverted.
+
+ */
+QPoint VectorscopeGenerator::mapToCircle(const QSize &targetSize, const QPointF &point) const
+{
+    return QPoint( (targetSize.width() -1) *      (point.x()+1)/2,
+                   (targetSize.height()-1) * (1 - (point.y()+1)/2) );
+}
+
+QImage VectorscopeGenerator::calculateVectorscope(const QSize &vectorscopeSize, const QImage &image, const float &gain,
+                                                  const VectorscopeGenerator::PaintMode &paintMode, const bool &drawAxis,
+                                                  const uint &accelFactor) const
+{
+    // Prepare the vectorscope data
+    const int cw = (vectorscopeSize.width() < vectorscopeSize.height()) ? vectorscopeSize.width() : vectorscopeSize.height();
+    QImage scope = QImage(cw, cw, QImage::Format_ARGB32);
+    scope.fill(qRgba(0,0,0,0));
+
+    const uchar *bits = image.bits();
+
+    int r,g,b;
+    double dy, dr, dg, db, dmax;
+    double y,u,v;
+    QPoint pt;
+    QRgb px;
+
+    const int stepsize = 4*accelFactor;
+
+    // Just an average for the number of image pixels per scope pixel.
+    // NOTE: byteCount() has to be replaced by (img.bytesPerLine()*img.height()) for Qt 4.5 to compile, see: http://doc.trolltech.org/4.6/qimage.html#bytesPerLine
+    double avgPxPerPx = (double) 4*(image.bytesPerLine()*image.height())/scope.size().width()/scope.size().height()/accelFactor;
+    qDebug() << "Expecting " << avgPxPerPx << " pixels per pixel.";
+
+    for (int i = 0; i < (image.bytesPerLine()*image.height()); i+= stepsize) {
+        QRgb *col = (QRgb *) bits;
+
+        r = qRed(*col);
+        g = qGreen(*col);
+        b = qBlue(*col);
+
+        y = (double)  0.001173 * r +0.002302 * g +0.0004471* b;
+        u = (double) -0.0005781* r -0.001135 * g +0.001713 * b;
+        v = (double)  0.002411 * r -0.002019 * g -0.0003921* b;
+
+        pt = mapToCircle(vectorscopeSize, QPointF(SCALING*gain*u, SCALING*gain*v));
+
+        if (!(pt.x() <= vectorscopeSize.width() && pt.x() >= 0
+            && pt.y() <= vectorscopeSize.height() && pt.y() >= 0)) {
+            // Point lies outside (because of scaling), don't plot it
+
+        } else {
+
+            // Draw the pixel using the chosen draw mode.
+            switch (paintMode) {
+            case PaintMode_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 PaintMode_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;
+                dmax = 255/dmax;
+
+                dr *= dmax;
+                dg *= dmax;
+                db *= dmax;
+
+                scope.setPixel(pt, qRgba(dr, dg, db, 255));
+                break;
+            case PaintMode_Original:
+                scope.setPixel(pt, *col);
+                break;
+            case PaintMode_Green:
+                px = scope.pixel(pt);
+                scope.setPixel(pt, qRgba(qRed(px)+(255-qRed(px))/(3*avgPxPerPx), qGreen(px)+20*(255-qGreen(px))/(avgPxPerPx),
+                                         qBlue(px)+(255-qBlue(px))/(avgPxPerPx), qAlpha(px)+(255-qAlpha(px))/(avgPxPerPx)));
+                break;
+            case PaintMode_Green2:
+                px = scope.pixel(pt);
+                scope.setPixel(pt, qRgba(qRed(px)+ceil((255-(float)qRed(px))/(4*avgPxPerPx)), 255,
+                                         qBlue(px)+ceil((255-(float)qBlue(px))/(avgPxPerPx)), qAlpha(px)+ceil((255-(float)qAlpha(px))/(avgPxPerPx))));
+                break;
+            case PaintMode_Black:
+                px = scope.pixel(pt);
+                scope.setPixel(pt, qRgba(0,0,0, qAlpha(px)+(255-qAlpha(px))/20));
+                break;
+            }
+        }
+
+        bits += stepsize;
+    }
+    return scope;
+}
diff --git a/src/colorcorrection/vectorscopegenerator.h b/src/colorcorrection/vectorscopegenerator.h
new file mode 100644 (file)
index 0000000..1f6851d
--- /dev/null
@@ -0,0 +1,40 @@
+/***************************************************************************
+ *   Copyright (C) 2010 by Simon Andreas Eugster (simon.eu@gmail.com)      *
+ *   This file is part of kdenlive. See www.kdenlive.org.                  *
+ *                                                                         *
+ *   This program 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.                                   *
+ ***************************************************************************/
+
+#ifndef VECTORSCOPEGENERATOR_H
+#define VECTORSCOPEGENERATOR_H
+
+#include <QObject>
+
+class QImage;
+class QPoint;
+class QPointF;
+class QSize;
+
+class VectorscopeGenerator : public QObject
+{
+Q_OBJECT
+
+public:
+    enum PaintMode { PaintMode_Green, PaintMode_Green2, PaintMode_Original, PaintMode_Chroma, PaintMode_YUV, PaintMode_Black };
+
+    QImage calculateVectorscope(const QSize &vectorscopeSize, const QImage &image, const float &gain,
+                                const VectorscopeGenerator::PaintMode &paintMode,
+                                const bool&, const uint &accelFactor = 1) const;
+
+    QPoint mapToCircle(const QSize &targetSize, const QPointF &point) const;
+    static const float scaling;
+
+signals:
+    void signalCalculationFinished(QImage image, const uint &ms);
+
+};
+
+#endif // VECTORSCOPEGENERATOR_H
index 0575a1c500a66f1bf7bc1b3982535676c6f79058..648691c52f021920752e06968e06c52179c5a220 100644 (file)
@@ -8,10 +8,12 @@
  *   (at your option) any later version.                                   *
  ***************************************************************************/
 
-#include <QDebug>
-#include <QTime>
 #include <QColor>
+#include <QDebug>
+#include <QImage>
 #include <QPainter>
+#include <QSize>
+#include <QTime>
 
 #include "waveformgenerator.h"
 
index d3abd6047b2b8b673fc07381358780d5f7a87cd1..531c482000b8e707ee58cfeb194dbaa204397420 100644 (file)
@@ -12,8 +12,8 @@
 #define WAVEFORMGENERATOR_H
 
 #include <QObject>
-#include <QImage>
-#include <QSize>
+class QImage;
+class QSize;
 
 class WaveformGenerator : public QObject
 {
index 95515d5111b7ac5e4752a5f27b677b273f13f9e9..897d9cf7388b8e1bdc39027449998f03c5d5aed3 100644 (file)
@@ -8,53 +8,25 @@
  *   (at your option) any later version.                                   *
  ***************************************************************************/
 
-/**
-
-  Vectorscope.
-
-  The basis matrix for converting RGB to YUV is:
-
-mRgb2Yuv =                       r =
-
-   0.29900   0.58700   0.11400     1.00000
-  -0.14741  -0.28939   0.43680  x  0.00000
-   0.61478  -0.51480  -0.09998     0.00000
-
-  The resulting YUV value is then drawn on the circle
-  using U and V as coordinate values.
-
-  The maximum length of such an UV vector is reached
-  for the colors Red and Cyan: 0.632.
-  To make optimal use of space in the circle, this value
-  can be used for scaling.
-
-  As we are dealing with RGB values in a range of {0,...,255}
-  and the conversion values are made for [0,1], we already
-  divide the conversion values by 255 previously, e.g. in
-  GNU octave.
-
-  See also:
-    http://de.wikipedia.org/wiki/Vektorskop
-    http://www.elektroniktutor.de/techno/vektskop.html
-
- */
 
+#include <QAction>
 #include <QColor>
+#include <QDebug>
 #include <QMouseEvent>
+#include <QMenu>
 #include <QPainter>
-#include <QDebug>
-#include <QAction>
 
-#include <qtconcurrentrun.h>
-#include <QThread>
 #include <QTime>
 
+#include "colorplaneexport.h"
+#include "colortools.h"
+#include "renderer.h"
 #include "vectorscope.h"
+#include "vectorscopegenerator.h"
 
-const float SCALING = 1/.7; // See class docs
 const float P75 = .75;
 const unsigned char DEFAULT_Y = 255;
-const unsigned int REALTIME_FPS = 15; // in fps.
+//const unsigned int REALTIME_FPS = 15; // in fps.
 
 const QPointF YUV_R(-.147,  .615);
 const QPointF YUV_G(-.289, -.515);
@@ -70,374 +42,243 @@ const QPen penDark(QBrush(QColor(0,0,20,250)), 1, Qt::SolidLine);
 
 
 Vectorscope::Vectorscope(Monitor *projMonitor, Monitor *clipMonitor, QWidget *parent) :
-    QWidget(parent),
-    m_projMonitor(projMonitor),
-    m_clipMonitor(clipMonitor),
-    m_activeRender(clipMonitor->render),
-    m_scaling(1),
-    m_skipPixels(1),
-    circleEnabled(false),
-    initialDimensionUpdateDone(false),
-    semaphore(1)
+    AbstractScopeWidget(projMonitor, clipMonitor, parent),
+    m_gain(1),
+    m_circleEnabled(false)
 {
-    setupUi(this);
+    ui = new Ui::Vectorscope_UI();
+    ui->setupUi(this);
 
     //TODO don't draw circle when mouseLeaved
 
     m_colorTools = new ColorTools();
     m_colorPlaneExport = new ColorPlaneExport(this);
+    m_vectorscopeGenerator = new VectorscopeGenerator();
 
-    paintMode->addItem(i18n("Green"), QVariant(PAINT_GREEN));
-    paintMode->addItem(i18n("Green 2"), QVariant(PAINT_GREEN2));
-    paintMode->addItem(i18n("Black"), QVariant(PAINT_BLACK));
-    paintMode->addItem(i18n("Modified YUV (Chroma)"), QVariant(PAINT_CHROMA));
-    paintMode->addItem(i18n("YUV"), QVariant(PAINT_YUV));
-    paintMode->addItem(i18n("Original Color"), QVariant(PAINT_ORIG));
+    ui->paintMode->addItem(i18n("Green 2"), QVariant(VectorscopeGenerator::PaintMode_Green2));
+    ui->paintMode->addItem(i18n("Green"), QVariant(VectorscopeGenerator::PaintMode_Green));
+    ui->paintMode->addItem(i18n("Black"), QVariant(VectorscopeGenerator::PaintMode_Black));
+    ui->paintMode->addItem(i18n("Modified YUV (Chroma)"), QVariant(VectorscopeGenerator::PaintMode_Chroma));
+    ui->paintMode->addItem(i18n("YUV"), QVariant(VectorscopeGenerator::PaintMode_YUV));
+    ui->paintMode->addItem(i18n("Original Color"), QVariant(VectorscopeGenerator::PaintMode_Original));
 
-    backgroundMode->addItem(i18n("None"), QVariant(BG_NONE));
-    backgroundMode->addItem(i18n("YUV"), QVariant(BG_YUV));
-    backgroundMode->addItem(i18n("Modified YUV (Chroma)"), QVariant(BG_CHROMA));
+    ui->backgroundMode->addItem(i18n("None"), QVariant(BG_NONE));
+    ui->backgroundMode->addItem(i18n("YUV"), QVariant(BG_YUV));
+    ui->backgroundMode->addItem(i18n("Modified YUV (Chroma)"), QVariant(BG_CHROMA));
 
-//    cbAutoRefresh->setChecked(true);
+    ui->sliderGain->setMinimum(0);
+    ui->sliderGain->setMaximum(40);
 
-    connect(backgroundMode, SIGNAL(currentIndexChanged(int)), this, SLOT(slotBackgroundChanged()));
-    connect(cbMagnify, SIGNAL(stateChanged(int)), this, SLOT(slotMagnifyChanged()));
-    connect(this, SIGNAL(signalScopeCalculationFinished(uint,uint)), this, SLOT(slotScopeCalculationFinished(uint,uint)));
-    connect(m_colorTools, SIGNAL(signalYuvWheelCalculationFinished()), 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);
+    bool b = true;
+    b &= connect(ui->backgroundMode, SIGNAL(currentIndexChanged(int)), this, SLOT(slotBackgroundChanged()));
+    b &= connect(ui->sliderGain, SIGNAL(valueChanged(int)), this, SLOT(slotGainChanged(int)));
+    b &= connect(ui->paintMode, SIGNAL(currentIndexChanged(int)), this, SLOT(forceUpdateScope()));
+    ui->sliderGain->setValue(0);
 
 
     ///// Build context menu /////
-    setContextMenuPolicy(Qt::ActionsContextMenu);
+
+    m_menu->addSeparator();
 
     m_aExportBackground = new QAction(i18n("Export background"), this);
-    addAction(m_aExportBackground);
-    connect(m_aExportBackground, SIGNAL(triggered()), this, SLOT(slotExportBackground()));
+    m_menu->addAction(m_aExportBackground);
+    b &= connect(m_aExportBackground, SIGNAL(triggered()), this, SLOT(slotExportBackground()));
 
     m_a75PBox = new QAction(i18n("75% box"), this);
     m_a75PBox->setCheckable(true);
     m_a75PBox->setChecked(false);
-    addAction(m_a75PBox);
-    connect(m_a75PBox, SIGNAL(changed()), this, SLOT(update()));
+    m_menu->addAction(m_a75PBox);
+    b &= connect(m_a75PBox, SIGNAL(changed()), this, SLOT(forceUpdateBackground()));
 
     m_aAxisEnabled = new QAction(i18n("Draw axis"), this);
     m_aAxisEnabled->setCheckable(true);
     m_aAxisEnabled->setChecked(false);
-    addAction(m_aAxisEnabled);
-    connect(m_aAxisEnabled, SIGNAL(changed()), this, SLOT(update()));
+    m_menu->addAction(m_aAxisEnabled);
+    b &= connect(m_aAxisEnabled, SIGNAL(changed()), this, SLOT(forceUpdateBackground()));
 
-    m_aRealtime = new QAction(i18n("Realtime (with precision loss)"), this);
-    m_aRealtime->setCheckable(true);
-    m_aRealtime->setChecked(false);
-    addAction(m_aRealtime);
+    Q_ASSERT(b);
 
 
     this->setMouseTracking(true);
-    updateDimensions();
-    prodWheelThread();
+    slotGainChanged(ui->sliderGain->value());
 }
 
 Vectorscope::~Vectorscope()
 {
+    delete m_colorTools;
     delete m_colorPlaneExport;
+    delete m_vectorscopeGenerator;
+
+    delete m_aExportBackground;
+    delete m_aAxisEnabled;
+    delete m_a75PBox;
+}
+
+QString Vectorscope::widgetName() const {
+    return QString("Vectorscope");
 }
 
-/**
+QRect Vectorscope::scopeRect()
+{
+    // Widget width/height
+    int ww = this->size().width();
+    int wh = this->size().height();
+
+    // Distance from top/left/right
+    int offset = 6;
 
-  Input point is on [-1,1]², 0 being at the center,
-  and positive directions are top/right.
+    // We want to paint below the controls area. The line is the lowest element.
+    QPoint topleft(offset, ui->line->y()+offset);
 
-  Maps to a QRect «inside» which is using the
-  0 point in the top left corner. The coordinates of
-  the rect «inside» are relative to «parent». The
-  coordinates returned can be used in the parent.
+    // Circle Width: min of width and height
+    cw = wh - topleft.y();
+    if (ww < cw) { cw = ww; }
+    cw -= 2*offset;
 
+    QRect scopeRect(topleft, QPoint(cw, cw) + topleft);
 
-    parent v
-  +-------------------+
-  | inside v          |
-  | +-----------+     |
-  | |    +      |     |
-  | |  --0++    |     | < point
-  | |    -      |     |
-  | +-----------+     |
-  |                   |
-  +-------------------+
+    m_centerPoint = m_vectorscopeGenerator->mapToCircle(scopeRect.size(), QPointF(0,0));
+    pR75 = m_vectorscopeGenerator->mapToCircle(scopeRect.size(), P75*VectorscopeGenerator::scaling*YUV_R);
+    pG75 = m_vectorscopeGenerator->mapToCircle(scopeRect.size(), P75*VectorscopeGenerator::scaling*YUV_G);
+    pB75 = m_vectorscopeGenerator->mapToCircle(scopeRect.size(), P75*VectorscopeGenerator::scaling*YUV_B);
+    pCy75 = m_vectorscopeGenerator->mapToCircle(scopeRect.size(), P75*VectorscopeGenerator::scaling*YUV_Cy);
+    pMg75 = m_vectorscopeGenerator->mapToCircle(scopeRect.size(), P75*VectorscopeGenerator::scaling*YUV_Mg);
+    pYl75 = m_vectorscopeGenerator->mapToCircle(scopeRect.size(), P75*VectorscopeGenerator::scaling*YUV_Yl);
 
- */
-QPoint Vectorscope::mapToCanvas(QRect inside, QPointF point)
-{
-    return QPoint(point.x()/2*inside.width()  + inside.width()/2  + inside.x(),
-                 -point.y()/2*inside.height() + inside.height()/2 + inside.y());
+    return scopeRect;
 }
 
-bool Vectorscope::prodCalcThread()
+bool Vectorscope::isHUDDependingOnInput() const { return false; }
+bool Vectorscope::isScopeDependingOnInput() const { return true; }
+bool Vectorscope::isBackgroundDependingOnInput() const { return false; }
+
+QImage Vectorscope::renderHUD(uint)
 {
-    bool ok = false;
-    if (this->visibleRegion().isEmpty()) {
-        qDebug() << "Nothing to see here. Other widget lying on top of Vectorscope. No need to render.";
-        ok = false;
-    } else if (m_scopeCalcThread.isRunning()) {
-        qDebug() << "Calc thread still running.";
-        ok = false;
-    } else {
 
-        // Acquire the semaphore. Don't release it anymore until the QFuture m_scopeCalcThread
-        // tells us that the thread has finished.
-        ok = semaphore.tryAcquire(1);
-        if (ok) {
-            qDebug() << "Calc thread not running anymore, finished: " << m_scopeCalcThread.isFinished() << ", Starting new thread";
+    QImage hud;
 
-            // See http://doc.qt.nokia.com/latest/qtconcurrentrun.html#run about
-            // running member functions in a thread
-            m_scopeCalcThread = QtConcurrent::run(this, &Vectorscope::calculateScope);
+    if (m_circleEnabled) {
+        // Mouse moved: Draw a circle over the scope
 
-            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
-        } else {
-            qDebug() << "Could not acquire semaphore. Deadlock avoided? Not starting new thread.";
-        }
-    }
-    return ok;
-}
+        hud = QImage(m_scopeRect.size(), QImage::Format_ARGB32);
+        hud.fill(qRgba(0, 0, 0, 0));
 
-bool Vectorscope::prodWheelThread()
-{
-    if (m_wheelCalcThread.isRunning()) {
-        qDebug() << "Wheel thread still running.";
-        return false;
-    } else {
-        switch (backgroundMode->itemData(backgroundMode->currentIndex()).toInt()) {
+        QPainter davinci(&hud);
+        QPoint widgetCenterPoint = m_scopeRect.topLeft() + m_centerPoint;
+
+        int dx = -widgetCenterPoint.x()+m_mousePos.x();
+        int dy =  widgetCenterPoint.y()-m_mousePos.y();
+
+        QPoint reference = m_vectorscopeGenerator->mapToCircle(m_scopeRect.size(), QPointF(1,0));
+
+        float r = sqrt(dx*dx + dy*dy);
+        float percent = (float) 100*r/VectorscopeGenerator::scaling/m_gain/(reference.x() - widgetCenterPoint.x());
+
+        switch (ui->backgroundMode->itemData(ui->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(m_colorTools, &ColorTools::yuvColorWheel, m_scopeRect.size(), (unsigned char) 128, 1/SCALING, false, true);
+            davinci.setPen(penLight);
             break;
-        case BG_CHROMA:
-            m_wheelCalcThread = QtConcurrent::run(m_colorTools, &ColorTools::yuvColorWheel, m_scopeRect.size(), (unsigned char) 255, 1/SCALING, true, true);
+        default:
+            davinci.setPen(penDark);
             break;
         }
-        newWheelChanges.fetchAndStoreRelaxed(0);
-        return true;
-    }
-}
+        davinci.drawEllipse(m_centerPoint, (int)r, (int)r);
+        davinci.setPen(penThin);
+        davinci.drawText(QPoint(m_scopeRect.width()-40, m_scopeRect.height()), i18n("%1 \%", QString::number(percent, 'f', 0)));
 
+        float angle = copysign(acos(dx/r)*180/M_PI, dy);
+        davinci.drawText(QPoint(10, m_scopeRect.height()), i18n("%1°", QString::number(angle, 'f', 1)));
 
+        m_circleEnabled = false;
+    } else {
+        hud = QImage(0, 0, QImage::Format_ARGB32);
+    }
+    emit signalHUDRenderingFinished(0, 1);
+    return hud;
+}
 
-void Vectorscope::calculateScope()
+QImage Vectorscope::renderScope(uint accelerationFactor)
 {
-    qDebug() << "..Scope rendering starts now.";
     QTime start = QTime::currentTime();
-    unsigned int skipPixels = 1;
-    if (m_aRealtime->isChecked()) {
-        skipPixels = m_skipPixels;
-    }
-    const int stepsize = 4*skipPixels;
+    QImage scope;
 
     if (cw <= 0) {
         qDebug() << "Scope size not known yet. Aborting.";
     } else {
 
-        // Prepare the vectorscope data
-        QImage scope(cw, cw, QImage::Format_ARGB32);
-        scope.fill(qRgba(0,0,0,0));
-
-        QImage img(m_activeRender->extractFrame(m_activeRender->seekFramePosition()));
-        const uchar *bits = img.bits();
-
-        int r,g,b;
-        double dy, dr, dg, db, dmax;
-        double y,u,v;
-        QPoint pt;
-        QRgb px;
-
-        // Just an average for the number of image pixels per scope pixel.
-        // NOTE: byteCount() has to be replaced by (img.bytesPerLine()*img.height()) for Qt 4.5 to compile, see: http://doc.trolltech.org/4.6/qimage.html#bytesPerLine
-        double avgPxPerPx = (double) 4*(img.bytesPerLine()*img.height())/scope.size().width()/scope.size().height()/skipPixels;
-        qDebug() << "Expecting " << avgPxPerPx << " pixels per pixel.";
-
-        const QRect scopeRect(QPoint(0,0), scope.size());
-
-        for (int i = 0; i < (img.bytesPerLine()*img.height()); i+= stepsize) {
-            QRgb *col = (QRgb *) bits;
-
-            r = qRed(*col);
-            g = qGreen(*col);
-            b = qBlue(*col);
-
-            y = (double)  0.001173 * r +0.002302 * g +0.0004471* b;
-            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*m_scaling*u, SCALING*m_scaling*v));
-
-            if (!(pt.x() <= scopeRect.width() && pt.x() >= 0
-                && pt.y() <= scopeRect.height() && pt.y() >= 0)) {
-                // Point lies outside (because of scaling), don't plot it
-
-            } else {
-
-                // 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;
-                    dmax = 255/dmax;
-
-                    dr *= dmax;
-                    dg *= dmax;
-                    db *= dmax;
-
-                    scope.setPixel(pt, qRgba(dr, dg, db, 255));
-                    break;
-                case PAINT_ORIG:
-                    scope.setPixel(pt, *col);
-                    break;
-                case PAINT_GREEN:
-                    px = scope.pixel(pt);
-                    scope.setPixel(pt, qRgba(qRed(px)+(255-qRed(px))/(3*avgPxPerPx), qGreen(px)+20*(255-qGreen(px))/(avgPxPerPx),
-                                             qBlue(px)+(255-qBlue(px))/(avgPxPerPx), qAlpha(px)+(255-qAlpha(px))/(avgPxPerPx)));
-                    break;
-                case PAINT_GREEN2:
-                    px = scope.pixel(pt);
-                    scope.setPixel(pt, qRgba(qRed(px)+ceil((255-(float)qRed(px))/(4*avgPxPerPx)), 255,
-                                             qBlue(px)+ceil((255-(float)qBlue(px))/(avgPxPerPx)), qAlpha(px)+ceil((255-(float)qAlpha(px))/(avgPxPerPx))));
-                    break;
-                case PAINT_BLACK:
-                    px = scope.pixel(pt);
-                    scope.setPixel(pt, qRgba(0,0,0, qAlpha(px)+(255-qAlpha(px))/20));
-                    break;
-                }
-            }
-
-            bits += stepsize;
-        }
+        scope = m_vectorscopeGenerator->calculateVectorscope(m_scopeRect.size(),
+                                                             m_activeRender->extractFrame(m_activeRender->seekFramePosition()),
+                                                             m_gain, (VectorscopeGenerator::PaintMode) ui->paintMode->itemData(ui->paintMode->currentIndex()).toInt(),
+                                                             m_aAxisEnabled->isChecked(), accelerationFactor);
 
-        m_scope = scope;
     }
 
     unsigned int mseconds = start.msecsTo(QTime::currentTime());
-    qDebug() << "Scope rendered in " << mseconds << " ms. Sending finished signal.";
-    emit signalScopeCalculationFinished(mseconds, skipPixels);
-    qDebug() << "xxScope: Signal finished sent.";
+//    qDebug() << "Scope rendered in " << mseconds << " ms. Sending finished signal.";
+//    emit signalScopeCalculationFinished(mseconds, skipPixels);
+    emit signalScopeRenderingFinished(mseconds, accelerationFactor);
+//    qDebug() << "xxScope: Signal finished sent.";
+    return scope;
 }
 
-void Vectorscope::updateDimensions()
-{
-    // Widget width/height
-    int ww = this->size().width();
-    int wh = this->size().height();
-
-    // Distance from top/left/right
-    int offset = 6;
-
-    // controlsArea contains the controls at the top;
-    // We want to paint below
-    QPoint topleft(offset, controlsArea->geometry().height()+offset);
-
-    // Circle Width: min of width and height
-    cw = wh - topleft.y();
-    if (ww < cw) { cw = ww; }
-    cw -= 2*offset;
-    m_scopeRect = QRect(topleft, QPoint(cw, cw) + topleft);
-
-    centerPoint = mapToCanvas(m_scopeRect, QPointF(0,0));
-    pR75 = mapToCanvas(m_scopeRect, P75*SCALING*YUV_R);
-    pG75 = mapToCanvas(m_scopeRect, P75*SCALING*YUV_G);
-    pB75 = mapToCanvas(m_scopeRect, P75*SCALING*YUV_B);
-    pCy75 = mapToCanvas(m_scopeRect, P75*SCALING*YUV_Cy);
-    pMg75 = mapToCanvas(m_scopeRect, P75*SCALING*YUV_Mg);
-    pYl75 = mapToCanvas(m_scopeRect, P75*SCALING*YUV_Yl);
-}
-
-void Vectorscope::paintEvent(QPaintEvent *)
+QImage Vectorscope::renderBackground(uint)
 {
+    QTime start = QTime::currentTime();
+    start.start();
 
-    if (!initialDimensionUpdateDone) {
-        // This is a workaround.
-        // When updating the dimensions in the constructor, the size
-        // of the control items at the top are simply ignored! So do
-        // it here instead.
-        updateDimensions();
-        initialDimensionUpdateDone = true;
+    QImage bg;
+    switch (ui->backgroundMode->itemData(ui->backgroundMode->currentIndex()).toInt()) {
+    case BG_YUV:
+        qDebug() << "YUV background.";
+        bg = m_colorTools->yuvColorWheel(m_scopeRect.size(), (unsigned char) 128, 1/VectorscopeGenerator::scaling, false, true);
+        break;
+    case BG_CHROMA:
+        bg = m_colorTools->yuvColorWheel(m_scopeRect.size(), (unsigned char) 255, 1/VectorscopeGenerator::scaling, true, true);
+        break;
+    default:
+        qDebug() << "No background.";
+        bg = QImage(cw, cw, QImage::Format_ARGB32);
+        bg.fill(qRgba(0,0,0,0));
+        break;
     }
 
+
     // Draw the vectorscope circle
-    QPainter davinci(this);
+    QPainter davinci(&bg);
     QPoint vinciPoint;
 
 
     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);
+    davinci.drawEllipse(0, 0, cw, cw);
 
     // Draw RGB/CMY points with 100% chroma
-    vinciPoint = mapToCanvas(m_scopeRect, SCALING*YUV_R);
+    vinciPoint = m_vectorscopeGenerator->mapToCircle(m_scopeRect.size(), VectorscopeGenerator::scaling*YUV_R);
     davinci.drawEllipse(vinciPoint, 4,4);
     davinci.drawText(vinciPoint-QPoint(20, -10), "R");
 
-    vinciPoint = mapToCanvas(m_scopeRect, SCALING*YUV_G);
+    vinciPoint = m_vectorscopeGenerator->mapToCircle(m_scopeRect.size(), VectorscopeGenerator::scaling*YUV_G);
     davinci.drawEllipse(vinciPoint, 4,4);
     davinci.drawText(vinciPoint-QPoint(20, 0), "G");
 
-    vinciPoint = mapToCanvas(m_scopeRect, SCALING*YUV_B);
+    vinciPoint = m_vectorscopeGenerator->mapToCircle(m_scopeRect.size(), VectorscopeGenerator::scaling*YUV_B);
     davinci.drawEllipse(vinciPoint, 4,4);
     davinci.drawText(vinciPoint+QPoint(15, 10), "B");
 
-    vinciPoint = mapToCanvas(m_scopeRect, SCALING*YUV_Cy);
+    vinciPoint = m_vectorscopeGenerator->mapToCircle(m_scopeRect.size(), VectorscopeGenerator::scaling*YUV_Cy);
     davinci.drawEllipse(vinciPoint, 4,4);
     davinci.drawText(vinciPoint+QPoint(15, -5), "Cy");
 
-    vinciPoint = mapToCanvas(m_scopeRect, SCALING*YUV_Mg);
+    vinciPoint = m_vectorscopeGenerator->mapToCircle(m_scopeRect.size(), VectorscopeGenerator::scaling*YUV_Mg);
     davinci.drawEllipse(vinciPoint, 4,4);
     davinci.drawText(vinciPoint+QPoint(15, 10), "Mg");
 
-    vinciPoint = mapToCanvas(m_scopeRect, SCALING*YUV_Yl);
+    vinciPoint = m_vectorscopeGenerator->mapToCircle(m_scopeRect.size(), VectorscopeGenerator::scaling*YUV_Yl);
     davinci.drawEllipse(vinciPoint, 4,4);
     davinci.drawText(vinciPoint-QPoint(25, 0), "Yl");
 
-    switch (backgroundMode->itemData(backgroundMode->currentIndex()).toInt()) {
+    switch (ui->backgroundMode->itemData(ui->backgroundMode->currentIndex()).toInt()) {
     case BG_NONE:
         davinci.setPen(penLight);
         break;
@@ -448,12 +289,12 @@ void Vectorscope::paintEvent(QPaintEvent *)
 
     // Draw axis
     if (m_aAxisEnabled->isChecked()) {
-        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)));
+        davinci.drawLine(m_vectorscopeGenerator->mapToCircle(m_scopeRect.size(), QPointF(0,-.9)), m_vectorscopeGenerator->mapToCircle(m_scopeRect.size(), QPointF(0,.9)));
+        davinci.drawLine(m_vectorscopeGenerator->mapToCircle(m_scopeRect.size(), QPointF(-.9,0)), m_vectorscopeGenerator->mapToCircle(m_scopeRect.size(), QPointF(.9,0)));
     }
 
     // Draw center point
-    switch (backgroundMode->itemData(backgroundMode->currentIndex()).toInt()) {
+    switch (ui->backgroundMode->itemData(ui->backgroundMode->currentIndex()).toInt()) {
     case BG_CHROMA:
         davinci.setPen(penDark);
         break;
@@ -461,7 +302,7 @@ void Vectorscope::paintEvent(QPaintEvent *)
         davinci.setPen(penThin);
         break;
     }
-    davinci.drawEllipse(centerPoint, 5,5);
+    davinci.drawEllipse(m_centerPoint, 5,5);
 
 
     // Draw 75% box
@@ -483,47 +324,14 @@ void Vectorscope::paintEvent(QPaintEvent *)
     davinci.drawEllipse(pMg75, 3,3);
     davinci.drawEllipse(pYl75, 3,3);
 
-
-
-    // Draw the scope data (previously calculated in a separate thread)
-    davinci.drawImage(m_scopeRect.topLeft(), m_scope);
-
-
-    if (circleEnabled) {
-        // Mouse moved: Draw a circle over the scope
-
-        int dx = -centerPoint.x()+mousePos.x();
-        int dy =  centerPoint.y()-mousePos.y();
-
-        QPoint reference = mapToCanvas(m_scopeRect, QPointF(1,0));
-
-        float r = sqrt(dx*dx + dy*dy);
-        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, (int)r, (int)r);
-        davinci.setPen(penThin);
-        davinci.drawText(m_scopeRect.bottomRight()-QPoint(40,0), i18n("%1 \%", QString::number(percent, 'f', 0)));
-        
-        float angle = copysign(acos(dx/r)*180/M_PI, dy);
-        davinci.drawText(m_scopeRect.bottomLeft()+QPoint(10,0), i18n("%1°", QString::number(angle, 'f', 1)));
-
-        circleEnabled = false;
-    }
-
     // Draw realtime factor (number of skipped pixels)
     if (m_aRealtime->isChecked()) {
         davinci.setPen(penThin);
-        davinci.drawText(m_scopeRect.bottomRight()-QPoint(40,15), QVariant(m_skipPixels).toString().append("x"));
+        davinci.drawText(QPoint(m_scopeRect.width()-40, m_scopeRect.height()-15), QVariant(m_accelFactorScope).toString().append("x"));
     }
 
+    emit signalBackgroundRenderingFinished(start.elapsed(), 1);
+    return bg;
 }
 
 
@@ -531,107 +339,11 @@ void Vectorscope::paintEvent(QPaintEvent *)
 
 ///// Slots /////
 
-void Vectorscope::slotMagnifyChanged()
-{
-    if (cbMagnify->isChecked()) {
-        m_scaling = 1.4;
-    } else {
-        m_scaling = 1;
-    }
-    prodCalcThread();
-}
-
-void Vectorscope::slotActiveMonitorChanged(bool isClipMonitor)
-{
-    if (isClipMonitor) {
-        m_activeRender = m_clipMonitor->render;
-        disconnect(this, SLOT(slotRenderZoneUpdated()));
-        connect(m_activeRender, SIGNAL(rendererPosition(int)), this, SLOT(slotRenderZoneUpdated()));
-    } else {
-        m_activeRender = m_projMonitor->render;
-        disconnect(this, SLOT(slotRenderZoneUpdated()));
-        connect(m_activeRender, SIGNAL(rendererPosition(int)), this, SLOT(slotRenderZoneUpdated()));
-    }
-}
-
-void Vectorscope::slotRenderZoneUpdated()
+void Vectorscope::slotGainChanged(int newval)
 {
-    qDebug() << "Monitor incoming. New frames total: " << newFrames << ", visible: " << this->isVisible();
-    QRegion region = this->visibleRegion();
-    qDebug() << "Visible region: empty? " << region.isEmpty() << ", size: " << region.boundingRect().width() << "x" << region.boundingRect().height();
-    // Monitor has shown a new frame
-    newFrames.fetchAndAddRelaxed(1);
-    if (cbAutoRefresh->isChecked()) {
-        prodCalcThread();
-    }
-}
-
-void Vectorscope::slotScopeCalculationFinished(unsigned int mseconds, unsigned int skipPixels)
-{
-    qDebug() << "Received finished signal.";
-    if (!m_scopeCalcThread.isFinished()) {
-        // Wait for the thread to finish. Otherwise the scope might not get updated
-        // as prodCalcThread may see it still running.
-        QTime start = QTime::currentTime();
-        qDebug() << "Scope renderer has not finished yet although finished signal received, waiting ...";
-        m_scopeCalcThread.waitForFinished();
-        qDebug() << "Waiting for finish is over. Waited for " << start.msecsTo(QTime::currentTime()) << " ms";
-    }
-    semaphore.release();
-
-    this->update();
-    qDebug() << "Scope updated.";
-
-    if (m_aRealtime->isChecked()) {
-        m_skipPixels = ceil((float)REALTIME_FPS*mseconds*skipPixels/1000);
-        Q_ASSERT(m_skipPixels >= 1);
-        qDebug() << "Realtime checked. Switching from " << skipPixels << " to " << m_skipPixels;
-
-    } else {
-        qDebug() << "No realtime.";
-    }
-
-    // If auto-refresh is enabled and new frames are available,
-    // just start the next calculation.
-    if (newFrames > 0 && cbAutoRefresh->isChecked()) {
-        qDebug() << "Found more frames in the queue (prodding now): " << newFrames;
-        prodCalcThread();
-    } else if (newChanges > 0) {
-        qDebug() << newChanges << " changes (e.g. resize) in the meantime.";
-        prodCalcThread();
-    } else {
-        qDebug() << newFrames << " new frames, " << newChanges << " new changes. Not updating.";
-    }
-}
-
-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();
+    m_gain = 1 + (float)newval/10;
+    ui->lblGain->setText(QString::number(m_gain, 'f', 1) + "x");
+    forceUpdateScope();
 }
 
 void Vectorscope::slotExportBackground()
@@ -645,58 +357,34 @@ void Vectorscope::slotBackgroundChanged()
 {
     // Background changed, switch to a suitable color mode now
     int index;
-    switch (backgroundMode->itemData(backgroundMode->currentIndex()).toInt()) {
+    switch (ui->backgroundMode->itemData(ui->backgroundMode->currentIndex()).toInt()) {
     case BG_YUV:
-        index = paintMode->findData(QVariant(PAINT_BLACK));
+        index = ui->paintMode->findData(QVariant(VectorscopeGenerator::PaintMode_Black));
         if (index >= 0) {
-            paintMode->setCurrentIndex(index);
+            ui->paintMode->setCurrentIndex(index);
         }
         break;
 
     case BG_NONE:
-        if (paintMode->itemData(paintMode->currentIndex()).toInt() == PAINT_BLACK) {
-            index = paintMode->findData(QVariant(PAINT_GREEN));
-            paintMode->setCurrentIndex(index);
+        if (ui->paintMode->itemData(ui->paintMode->currentIndex()).toInt() == VectorscopeGenerator::PaintMode_Black) {
+            index = ui->paintMode->findData(QVariant(VectorscopeGenerator::PaintMode_Green2));
+            ui->paintMode->setCurrentIndex(index);
         }
         break;
     }
-    newWheelChanges.fetchAndAddAcquire(1);
-    prodWheelThread();
+    forceUpdateBackground();
 }
 
 
 
 ///// Events /////
 
-void Vectorscope::mousePressEvent(QMouseEvent *)
-{
-    // Update the scope on mouse press
-    prodCalcThread();
-}
-
 void Vectorscope::mouseMoveEvent(QMouseEvent *event)
 {
     // Draw a circle around the center,
     // showing percentage number of the radius length
 
-    circleEnabled = true;
-    mousePos = event->pos();
-    this->update();
-}
-
-void Vectorscope::resizeEvent(QResizeEvent *event)
-{
-    qDebug() << "Resized.";
-    updateDimensions();
-    newChanges.fetchAndAddAcquire(1);
-    prodCalcThread();
-    prodWheelThread();
-    QWidget::resizeEvent(event);
-}
-
-void Vectorscope::raise()
-{
-    qDebug() << "Raised. Prodding calc thread.";
-    prodCalcThread();
-    QWidget::raise();
+    m_circleEnabled = true;
+    m_mousePos = event->pos();
+    forceUpdateHUD();
 }
index 0a85045c302622fac3402c5eaf15f85ada87bcb6..de552524d836697bd585e8c62ef88ec633821da5 100644 (file)
 #define VECTORSCOPE_H
 
 #include <QtCore>
-#include "renderer.h"
-#include "monitor.h"
-#include "colorplaneexport.h"
-#include "colortools.h"
 #include "ui_vectorscope_ui.h"
+#include "abstractscopewidget.h"
 
+class ColorPlaneExport;
+class ColorTools;
 class Render;
 class Monitor;
 class Vectorscope_UI;
+class VectorscopeGenerator;
 
-enum PAINT_MODE { PAINT_GREEN = 0, PAINT_ORIG = 1, PAINT_CHROMA = 2, PAINT_YUV = 3, PAINT_BLACK = 4, PAINT_GREEN2 = 5 };
 enum BACKGROUND_MODE { BG_NONE = 0, BG_YUV = 1, BG_CHROMA = 2 };
 
-class Vectorscope : public QWidget, public Ui::Vectorscope_UI {
+class Vectorscope : public AbstractScopeWidget {
     Q_OBJECT
 
 public:
     Vectorscope(Monitor *projMonitor, Monitor *clipMonitor, QWidget *parent = 0);
     ~Vectorscope();
 
+    QString widgetName() const;
+
 protected:
-    void paintEvent(QPaintEvent *);
-    void resizeEvent(QResizeEvent *event);
-    void mousePressEvent(QMouseEvent *);
-    void mouseMoveEvent(QMouseEvent *event);
-    void raise();
+    void mouseMoveEvent(QMouseEvent *);
+
+
+    ///// Implemented methods /////
+    QRect scopeRect();
+    QImage renderHUD(uint accelerationFactor);
+    QImage renderScope(uint accelerationFactor);
+    QImage renderBackground(uint accelerationFactor);
+    bool isHUDDependingOnInput() const;
+    bool isScopeDependingOnInput() const;
+    bool isBackgroundDependingOnInput() const;
 
 private:
-    Monitor *m_projMonitor;
-    Monitor *m_clipMonitor;
-    Render *m_activeRender;
+    Ui::Vectorscope_UI *ui;
 
     ColorTools *m_colorTools;
     ColorPlaneExport *m_colorPlaneExport;
@@ -50,74 +55,40 @@ private:
     QAction *m_aExportBackground;
     QAction *m_aAxisEnabled;
     QAction *m_a75PBox;
-    QAction *m_aRealtime;
+
+    VectorscopeGenerator *m_vectorscopeGenerator;
 
     /** How to represent the pixels on the scope (green, original color, ...) */
     int iPaintMode;
 
     /** Custom scaling of the vectorscope */
-    float m_scaling;
-
-    /** Number of pixels to skip for getting realtime updates */
-    int m_skipPixels;
+    float m_gain;
 
-    QPoint mapToCanvas(QRect inside, QPointF point);
-    QPoint centerPoint, pR75, pG75, pB75, pCy75, pMg75, pYl75;
+    QPoint m_centerPoint, pR75, pG75, pB75, pCy75, pMg75, pYl75;
 
-    bool circleEnabled;
-    QPoint mousePos;
+    bool m_circleEnabled;
+    QPoint m_mousePos;
 
     /** Updates the dimension. Only necessary when the widget has been resized. */
     void updateDimensions();
-    bool initialDimensionUpdateDone;
-    QRect m_scopeRect;
     int cw;
 
-
-    /** 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;
-
-    /** This semaphore that guards QFuture m_scopeCalcThread is necessary for avoiding
-        deadlocks. If not present, then an incoming new frame might trigger a new thread
-        at the wrong point in time, causing a deadlock. Nasty ;) */
-    QSemaphore semaphore;
-
-    /** 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
       current frame. */
-    QAtomicInt newFrames;
+//    QAtomicInt newFrames;
     /** Counts the number of other changes that should cause the vectorscope to be
       recalculated. This is for example a resizeEvent. In this case, no new frames
       are generated, but the scope has to be updated in any case (also if auto-update
       is not enabled). */
-    QAtomicInt newChanges;
+//    QAtomicInt newChanges;
     /** Counts the number of changes concerning the background wheel */
-    QAtomicInt newWheelChanges;
+//    QAtomicInt newWheelChanges;
 
-signals:
-    void signalScopeCalculationFinished(const unsigned int &mseconds, const unsigned int &skipPixels);
 
 private slots:
-    void slotMagnifyChanged();
+    void slotGainChanged(int);
     void slotBackgroundChanged();
-    void slotActiveMonitorChanged(bool isClipMonitor);
-    void slotRenderZoneUpdated();
-    void slotScopeCalculationFinished(unsigned int mseconds, unsigned int skipPixels);
-    void slotWheelCalculationFinished();
-    void slotUpdateScope();
-    void slotUpdateWheel();
     void slotExportBackground();
 };
 
index 8084ba6c69984ed15c26e0e262cd257bbd31d25e..35ff33c43a0e82b6315ab76e422517750e083505 100644 (file)
     <height>450</height>
    </size>
   </property>
-  <property name="palette">
-   <palette>
-    <active>
-     <colorrole role="WindowText">
-      <brush brushstyle="SolidPattern">
-       <color alpha="255">
-        <red>250</red>
-        <green>238</green>
-        <blue>226</blue>
-       </color>
-      </brush>
-     </colorrole>
-     <colorrole role="Button">
-      <brush brushstyle="SolidPattern">
-       <color alpha="255">
-        <red>40</red>
-        <green>40</green>
-        <blue>39</blue>
-       </color>
-      </brush>
-     </colorrole>
-     <colorrole role="Text">
-      <brush brushstyle="SolidPattern">
-       <color alpha="255">
-        <red>250</red>
-        <green>238</green>
-        <blue>226</blue>
-       </color>
-      </brush>
-     </colorrole>
-     <colorrole role="ButtonText">
-      <brush brushstyle="SolidPattern">
-       <color alpha="255">
-        <red>250</red>
-        <green>238</green>
-        <blue>226</blue>
-       </color>
-      </brush>
-     </colorrole>
-     <colorrole role="Base">
-      <brush brushstyle="SolidPattern">
-       <color alpha="255">
-        <red>40</red>
-        <green>40</green>
-        <blue>39</blue>
-       </color>
-      </brush>
-     </colorrole>
-     <colorrole role="Window">
-      <brush brushstyle="SolidPattern">
-       <color alpha="255">
-        <red>40</red>
-        <green>40</green>
-        <blue>39</blue>
-       </color>
-      </brush>
-     </colorrole>
-    </active>
-    <inactive>
-     <colorrole role="WindowText">
-      <brush brushstyle="SolidPattern">
-       <color alpha="255">
-        <red>250</red>
-        <green>238</green>
-        <blue>226</blue>
-       </color>
-      </brush>
-     </colorrole>
-     <colorrole role="Button">
-      <brush brushstyle="SolidPattern">
-       <color alpha="255">
-        <red>40</red>
-        <green>40</green>
-        <blue>39</blue>
-       </color>
-      </brush>
-     </colorrole>
-     <colorrole role="Text">
-      <brush brushstyle="SolidPattern">
-       <color alpha="255">
-        <red>250</red>
-        <green>238</green>
-        <blue>226</blue>
-       </color>
-      </brush>
-     </colorrole>
-     <colorrole role="ButtonText">
-      <brush brushstyle="SolidPattern">
-       <color alpha="255">
-        <red>250</red>
-        <green>238</green>
-        <blue>226</blue>
-       </color>
-      </brush>
-     </colorrole>
-     <colorrole role="Base">
-      <brush brushstyle="SolidPattern">
-       <color alpha="255">
-        <red>40</red>
-        <green>40</green>
-        <blue>39</blue>
-       </color>
-      </brush>
-     </colorrole>
-     <colorrole role="Window">
-      <brush brushstyle="SolidPattern">
-       <color alpha="255">
-        <red>40</red>
-        <green>40</green>
-        <blue>39</blue>
-       </color>
-      </brush>
-     </colorrole>
-    </inactive>
-    <disabled>
-     <colorrole role="WindowText">
-      <brush brushstyle="SolidPattern">
-       <color alpha="255">
-        <red>146</red>
-        <green>145</green>
-        <blue>144</blue>
-       </color>
-      </brush>
-     </colorrole>
-     <colorrole role="Button">
-      <brush brushstyle="SolidPattern">
-       <color alpha="255">
-        <red>40</red>
-        <green>40</green>
-        <blue>39</blue>
-       </color>
-      </brush>
-     </colorrole>
-     <colorrole role="Text">
-      <brush brushstyle="SolidPattern">
-       <color alpha="255">
-        <red>165</red>
-        <green>164</green>
-        <blue>164</blue>
-       </color>
-      </brush>
-     </colorrole>
-     <colorrole role="ButtonText">
-      <brush brushstyle="SolidPattern">
-       <color alpha="255">
-        <red>151</red>
-        <green>150</green>
-        <blue>149</blue>
-       </color>
-      </brush>
-     </colorrole>
-     <colorrole role="Base">
-      <brush brushstyle="SolidPattern">
-       <color alpha="255">
-        <red>40</red>
-        <green>40</green>
-        <blue>39</blue>
-       </color>
-      </brush>
-     </colorrole>
-     <colorrole role="Window">
-      <brush brushstyle="SolidPattern">
-       <color alpha="255">
-        <red>40</red>
-        <green>40</green>
-        <blue>39</blue>
-       </color>
-      </brush>
-     </colorrole>
-    </disabled>
-   </palette>
-  </property>
   <property name="windowTitle">
    <string>Form</string>
   </property>
   <layout class="QGridLayout" name="gridLayout">
    <item row="0" column="0">
-    <layout class="QVBoxLayout" name="controlsArea">
-     <item>
-      <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">
-       <item>
-        <widget class="QCheckBox" name="cbMagnify">
-         <property name="text">
-          <string>Magnify</string>
-         </property>
-        </widget>
-       </item>
-       <item>
-        <widget class="QCheckBox" name="cbAutoRefresh">
-         <property name="text">
-          <string>Auto-Refresh</string>
-         </property>
-        </widget>
-       </item>
-      </layout>
-     </item>
-    </layout>
+    <widget class="QLabel" name="lblPaintMode">
+     <property name="text">
+      <string>Paint mode</string>
+     </property>
+    </widget>
+   </item>
+   <item row="0" column="1" colspan="2">
+    <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="1" column="0">
+    <widget class="QLabel" name="lblBackground">
+     <property name="text">
+      <string>Background</string>
+     </property>
+    </widget>
+   </item>
+   <item row="1" column="1" colspan="2">
+    <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="2" column="0">
+    <widget class="QLabel" name="label">
+     <property name="text">
+      <string>Gain</string>
+     </property>
+    </widget>
+   </item>
+   <item row="2" column="1">
+    <widget class="QSlider" name="sliderGain">
+     <property name="orientation">
+      <enum>Qt::Horizontal</enum>
+     </property>
+    </widget>
+   </item>
+   <item row="3" column="0" colspan="3">
+    <widget class="Line" name="line">
+     <property name="orientation">
+      <enum>Qt::Horizontal</enum>
+     </property>
+    </widget>
+   </item>
+   <item row="4" column="0" colspan="2">
     <spacer name="verticalSpacer">
      <property name="orientation">
       <enum>Qt::Vertical</enum>
      </property>
     </spacer>
    </item>
+   <item row="2" column="2">
+    <widget class="QLabel" name="lblGain">
+     <property name="sizePolicy">
+      <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
+       <horstretch>0</horstretch>
+       <verstretch>0</verstretch>
+      </sizepolicy>
+     </property>
+     <property name="minimumSize">
+      <size>
+       <width>40</width>
+       <height>0</height>
+      </size>
+     </property>
+     <property name="text">
+      <string notr="true">(notr)</string>
+     </property>
+     <property name="alignment">
+      <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+     </property>
+    </widget>
+   </item>
   </layout>
  </widget>
  <resources/>